--- a/net/minecraft/world/entity/vehicle/AbstractMinecart.java
+++ b/net/minecraft/world/entity/vehicle/AbstractMinecart.java
@@ -3,7 +3,9 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
+import com.google.common.collect.UnmodifiableIterator;
 import com.mojang.datafixers.util.Pair;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import javax.annotation.Nullable;
@@ -32,6 +34,7 @@
 import net.minecraft.world.entity.npc.Villager;
 import net.minecraft.world.entity.npc.WanderingTrader;
 import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.Item;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.Items;
 import net.minecraft.world.level.Level;
@@ -43,15 +46,21 @@
 import net.minecraft.world.level.block.state.properties.RailShape;
 import net.minecraft.world.phys.AABB;
 import net.minecraft.world.phys.Vec3;
+// CraftBukkit start
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.util.CraftLocation;
+import org.bukkit.entity.Vehicle;
+import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
+import org.bukkit.util.Vector;
+// CraftBukkit end
 
 public abstract class AbstractMinecart extends VehicleEntity {
-    private static final Vec3 LOWERED_PASSENGER_ATTACHMENT = new Vec3(0.0, 0.0, 0.0);
+
+    private static final Vec3 LOWERED_PASSENGER_ATTACHMENT = new Vec3(0.0D, 0.0D, 0.0D);
     private static final EntityDataAccessor<Integer> DATA_ID_DISPLAY_BLOCK = SynchedEntityData.defineId(AbstractMinecart.class, EntityDataSerializers.INT);
     private static final EntityDataAccessor<Integer> DATA_ID_DISPLAY_OFFSET = SynchedEntityData.defineId(AbstractMinecart.class, EntityDataSerializers.INT);
     private static final EntityDataAccessor<Boolean> DATA_ID_CUSTOM_DISPLAY = SynchedEntityData.defineId(AbstractMinecart.class, EntityDataSerializers.BOOLEAN);
-    private static final ImmutableMap<Pose, ImmutableList<Integer>> POSE_DISMOUNT_HEIGHTS = ImmutableMap.of(
-        Pose.STANDING, ImmutableList.of(0, 1, -1), Pose.CROUCHING, ImmutableList.of(0, 1, -1), Pose.SWIMMING, ImmutableList.of(0, 1)
-    );
+    private static final ImmutableMap<Pose, ImmutableList<Integer>> POSE_DISMOUNT_HEIGHTS = ImmutableMap.of(Pose.STANDING, ImmutableList.of(0, 1, -1), Pose.CROUCHING, ImmutableList.of(0, 1, -1), Pose.SWIMMING, ImmutableList.of(0, 1));
     protected static final float WATER_SLOWDOWN_FACTOR = 0.95F;
     private boolean flipped;
     private boolean onRails;
@@ -61,30 +70,43 @@
     private double lerpZ;
     private double lerpYRot;
     private double lerpXRot;
-    private Vec3 targetDeltaMovement = Vec3.ZERO;
-    private static final Map<RailShape, Pair<Vec3i, Vec3i>> EXITS = Util.make(Maps.newEnumMap(RailShape.class), map -> {
-        Vec3i vec3i = Direction.WEST.getNormal();
-        Vec3i vec3i2 = Direction.EAST.getNormal();
-        Vec3i vec3i3 = Direction.NORTH.getNormal();
-        Vec3i vec3i4 = Direction.SOUTH.getNormal();
-        Vec3i vec3i5 = vec3i.below();
-        Vec3i vec3i6 = vec3i2.below();
-        Vec3i vec3i7 = vec3i3.below();
-        Vec3i vec3i8 = vec3i4.below();
-        map.put(RailShape.NORTH_SOUTH, Pair.of(vec3i3, vec3i4));
-        map.put(RailShape.EAST_WEST, Pair.of(vec3i, vec3i2));
-        map.put(RailShape.ASCENDING_EAST, Pair.of(vec3i5, vec3i2));
-        map.put(RailShape.ASCENDING_WEST, Pair.of(vec3i, vec3i6));
-        map.put(RailShape.ASCENDING_NORTH, Pair.of(vec3i3, vec3i8));
-        map.put(RailShape.ASCENDING_SOUTH, Pair.of(vec3i7, vec3i4));
-        map.put(RailShape.SOUTH_EAST, Pair.of(vec3i4, vec3i2));
-        map.put(RailShape.SOUTH_WEST, Pair.of(vec3i4, vec3i));
-        map.put(RailShape.NORTH_WEST, Pair.of(vec3i3, vec3i));
-        map.put(RailShape.NORTH_EAST, Pair.of(vec3i3, vec3i2));
+    private Vec3 targetDeltaMovement;
+    private static final Map<RailShape, Pair<Vec3i, Vec3i>> EXITS = (Map) Util.make(Maps.newEnumMap(RailShape.class), (enummap) -> {
+        Vec3i baseblockposition = Direction.WEST.getNormal();
+        Vec3i baseblockposition1 = Direction.EAST.getNormal();
+        Vec3i baseblockposition2 = Direction.NORTH.getNormal();
+        Vec3i baseblockposition3 = Direction.SOUTH.getNormal();
+        Vec3i baseblockposition4 = baseblockposition.below();
+        Vec3i baseblockposition5 = baseblockposition1.below();
+        Vec3i baseblockposition6 = baseblockposition2.below();
+        Vec3i baseblockposition7 = baseblockposition3.below();
+
+        enummap.put(RailShape.NORTH_SOUTH, Pair.of(baseblockposition2, baseblockposition3));
+        enummap.put(RailShape.EAST_WEST, Pair.of(baseblockposition, baseblockposition1));
+        enummap.put(RailShape.ASCENDING_EAST, Pair.of(baseblockposition4, baseblockposition1));
+        enummap.put(RailShape.ASCENDING_WEST, Pair.of(baseblockposition, baseblockposition5));
+        enummap.put(RailShape.ASCENDING_NORTH, Pair.of(baseblockposition2, baseblockposition7));
+        enummap.put(RailShape.ASCENDING_SOUTH, Pair.of(baseblockposition6, baseblockposition3));
+        enummap.put(RailShape.SOUTH_EAST, Pair.of(baseblockposition3, baseblockposition1));
+        enummap.put(RailShape.SOUTH_WEST, Pair.of(baseblockposition3, baseblockposition));
+        enummap.put(RailShape.NORTH_WEST, Pair.of(baseblockposition2, baseblockposition));
+        enummap.put(RailShape.NORTH_EAST, Pair.of(baseblockposition2, baseblockposition1));
     });
 
+    // CraftBukkit start
+    public boolean slowWhenEmpty = true;
+    private double derailedX = 0.5;
+    private double derailedY = 0.5;
+    private double derailedZ = 0.5;
+    private double flyingX = 0.95;
+    private double flyingY = 0.95;
+    private double flyingZ = 0.95;
+    public double maxSpeed = 0.4D;
+    // CraftBukkit end
+
     protected AbstractMinecart(EntityType<?> type, Level world) {
         super(type, world);
+        this.targetDeltaMovement = Vec3.ZERO;
         this.blocksBuilding = true;
     }
 
@@ -96,20 +118,36 @@
         this.zo = z;
     }
 
-    public static AbstractMinecart createMinecart(
-        ServerLevel world, double x, double y, double z, AbstractMinecart.Type type, ItemStack stack, @Nullable Player player
-    ) {
-        AbstractMinecart abstractMinecart = (AbstractMinecart)(switch (type) {
-            case CHEST -> new MinecartChest(world, x, y, z);
-            case FURNACE -> new MinecartFurnace(world, x, y, z);
-            case TNT -> new MinecartTNT(world, x, y, z);
-            case SPAWNER -> new MinecartSpawner(world, x, y, z);
-            case HOPPER -> new MinecartHopper(world, x, y, z);
-            case COMMAND_BLOCK -> new MinecartCommandBlock(world, x, y, z);
-            default -> new Minecart(world, x, y, z);
-        });
-        EntityType.<AbstractMinecart>createDefaultStackConfig(world, stack, player).accept(abstractMinecart);
-        return abstractMinecart;
+    public static AbstractMinecart createMinecart(ServerLevel world, double x, double y, double z, AbstractMinecart.Type type, ItemStack stack, @Nullable Player player) {
+        Object object;
+
+        switch (type.ordinal()) {
+            case 1:
+                object = new MinecartChest(world, x, y, z);
+                break;
+            case 2:
+                object = new MinecartFurnace(world, x, y, z);
+                break;
+            case 3:
+                object = new MinecartTNT(world, x, y, z);
+                break;
+            case 4:
+                object = new MinecartSpawner(world, x, y, z);
+                break;
+            case 5:
+                object = new MinecartHopper(world, x, y, z);
+                break;
+            case 6:
+                object = new MinecartCommandBlock(world, x, y, z);
+                break;
+            default:
+                object = new Minecart(world, x, y, z);
+        }
+
+        AbstractMinecart object1 = (AbstractMinecart) object; // CraftBukkit- decompile error
+
+        EntityType.createDefaultStackConfig(world, stack, player).accept(object1);
+        return (AbstractMinecart) object1;
     }
 
     @Override
@@ -120,18 +158,26 @@
     @Override
     protected void defineSynchedData(SynchedEntityData.Builder builder) {
         super.defineSynchedData(builder);
-        builder.define(DATA_ID_DISPLAY_BLOCK, Block.getId(Blocks.AIR.defaultBlockState()));
-        builder.define(DATA_ID_DISPLAY_OFFSET, 6);
-        builder.define(DATA_ID_CUSTOM_DISPLAY, false);
+        builder.define(AbstractMinecart.DATA_ID_DISPLAY_BLOCK, Block.getId(Blocks.AIR.defaultBlockState()));
+        builder.define(AbstractMinecart.DATA_ID_DISPLAY_OFFSET, 6);
+        builder.define(AbstractMinecart.DATA_ID_CUSTOM_DISPLAY, false);
     }
 
     @Override
     public boolean canCollideWith(Entity other) {
-        return Boat.canVehicleCollide(this, other);
+        // Paper start - fix VehicleEntityCollisionEvent not called when colliding with player
+        boolean collides = Boat.canVehicleCollide(this, other);
+        if (!collides) {
+            return false;
+        }
+        org.bukkit.event.vehicle.VehicleEntityCollisionEvent collisionEvent = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent((org.bukkit.entity.Vehicle) getBukkitEntity(), other.getBukkitEntity());
+
+        return collisionEvent.callEvent();
+        // Paper end - fix VehicleEntityCollisionEvent not called when colliding with player
     }
 
     @Override
-    public boolean isPushable() {
+    public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule
         return true;
     }
 
@@ -142,54 +188,71 @@
 
     @Override
     protected Vec3 getPassengerAttachmentPoint(Entity passenger, EntityDimensions dimensions, float scaleFactor) {
-        boolean bl = passenger instanceof Villager || passenger instanceof WanderingTrader;
-        return bl ? LOWERED_PASSENGER_ATTACHMENT : super.getPassengerAttachmentPoint(passenger, dimensions, scaleFactor);
+        boolean flag = passenger instanceof Villager || passenger instanceof WanderingTrader;
+
+        return flag ? AbstractMinecart.LOWERED_PASSENGER_ATTACHMENT : super.getPassengerAttachmentPoint(passenger, dimensions, scaleFactor);
     }
 
     @Override
     public Vec3 getDismountLocationForPassenger(LivingEntity passenger) {
-        Direction direction = this.getMotionDirection();
-        if (direction.getAxis() == Direction.Axis.Y) {
+        Direction enumdirection = this.getMotionDirection();
+
+        if (enumdirection.getAxis() == Direction.Axis.Y) {
             return super.getDismountLocationForPassenger(passenger);
         } else {
-            int[][] is = DismountHelper.offsetsForDirection(direction);
-            BlockPos blockPos = this.blockPosition();
-            BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
-            ImmutableList<Pose> immutableList = passenger.getDismountPoses();
+            int[][] aint = DismountHelper.offsetsForDirection(enumdirection);
+            BlockPos blockposition = this.blockPosition();
+            BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+            ImmutableList<Pose> immutablelist = passenger.getDismountPoses();
+            UnmodifiableIterator unmodifiableiterator = immutablelist.iterator();
 
-            for (Pose pose : immutableList) {
-                EntityDimensions entityDimensions = passenger.getDimensions(pose);
-                float f = Math.min(entityDimensions.width(), 1.0F) / 2.0F;
+            while (unmodifiableiterator.hasNext()) {
+                Pose entitypose = (Pose) unmodifiableiterator.next();
+                EntityDimensions entitysize = passenger.getDimensions(entitypose);
+                float f = Math.min(entitysize.width(), 1.0F) / 2.0F;
+                UnmodifiableIterator unmodifiableiterator1 = ((ImmutableList) AbstractMinecart.POSE_DISMOUNT_HEIGHTS.get(entitypose)).iterator();
 
-                for (int i : POSE_DISMOUNT_HEIGHTS.get(pose)) {
-                    for (int[] js : is) {
-                        mutableBlockPos.set(blockPos.getX() + js[0], blockPos.getY() + i, blockPos.getZ() + js[1]);
-                        double d = this.level()
-                            .getBlockFloorHeight(
-                                DismountHelper.nonClimbableShape(this.level(), mutableBlockPos),
-                                () -> DismountHelper.nonClimbableShape(this.level(), mutableBlockPos.below())
-                            );
-                        if (DismountHelper.isBlockFloorValid(d)) {
-                            AABB aABB = new AABB((double)(-f), 0.0, (double)(-f), (double)f, (double)entityDimensions.height(), (double)f);
-                            Vec3 vec3 = Vec3.upFromBottomCenterOf(mutableBlockPos, d);
-                            if (DismountHelper.canDismountTo(this.level(), passenger, aABB.move(vec3))) {
-                                passenger.setPose(pose);
-                                return vec3;
+                while (unmodifiableiterator1.hasNext()) {
+                    int i = (Integer) unmodifiableiterator1.next();
+                    int[][] aint1 = aint;
+                    int j = aint.length;
+
+                    for (int k = 0; k < j; ++k) {
+                        int[] aint2 = aint1[k];
+
+                        blockposition_mutableblockposition.set(blockposition.getX() + aint2[0], blockposition.getY() + i, blockposition.getZ() + aint2[1]);
+                        double d0 = this.level().getBlockFloorHeight(DismountHelper.nonClimbableShape(this.level(), blockposition_mutableblockposition), () -> {
+                            return DismountHelper.nonClimbableShape(this.level(), blockposition_mutableblockposition.below());
+                        });
+
+                        if (DismountHelper.isBlockFloorValid(d0)) {
+                            AABB axisalignedbb = new AABB((double) (-f), 0.0D, (double) (-f), (double) f, (double) entitysize.height(), (double) f);
+                            Vec3 vec3d = Vec3.upFromBottomCenterOf(blockposition_mutableblockposition, d0);
+
+                            if (DismountHelper.canDismountTo(this.level(), passenger, axisalignedbb.move(vec3d))) {
+                                passenger.setPose(entitypose);
+                                return vec3d;
                             }
                         }
                     }
                 }
             }
 
-            double e = this.getBoundingBox().maxY;
-            mutableBlockPos.set((double)blockPos.getX(), e, (double)blockPos.getZ());
+            double d1 = this.getBoundingBox().maxY;
 
-            for (Pose pose2 : immutableList) {
-                double g = (double)passenger.getDimensions(pose2).height();
-                int j = Mth.ceil(e - (double)mutableBlockPos.getY() + g);
-                double h = DismountHelper.findCeilingFrom(mutableBlockPos, j, pos -> this.level().getBlockState(pos).getCollisionShape(this.level(), pos));
-                if (e + g <= h) {
-                    passenger.setPose(pose2);
+            blockposition_mutableblockposition.set((double) blockposition.getX(), d1, (double) blockposition.getZ());
+            UnmodifiableIterator unmodifiableiterator2 = immutablelist.iterator();
+
+            while (unmodifiableiterator2.hasNext()) {
+                Pose entitypose1 = (Pose) unmodifiableiterator2.next();
+                double d2 = (double) passenger.getDimensions(entitypose1).height();
+                int l = Mth.ceil(d1 - (double) blockposition_mutableblockposition.getY() + d2);
+                double d3 = DismountHelper.findCeilingFrom(blockposition_mutableblockposition, l, (blockposition1) -> {
+                    return this.level().getBlockState(blockposition1).getCollisionShape(this.level(), blockposition1);
+                });
+
+                if (d1 + d2 <= d3) {
+                    passenger.setPose(entitypose1);
                     break;
                 }
             }
@@ -200,8 +263,9 @@
 
     @Override
     protected float getBlockSpeedFactor() {
-        BlockState blockState = this.level().getBlockState(this.blockPosition());
-        return blockState.is(BlockTags.RAILS) ? 1.0F : super.getBlockSpeedFactor();
+        BlockState iblockdata = this.level().getBlockState(this.blockPosition());
+
+        return iblockdata.is(BlockTags.RAILS) ? 1.0F : super.getBlockSpeedFactor();
     }
 
     @Override
@@ -217,7 +281,7 @@
     }
 
     private static Pair<Vec3i, Vec3i> exits(RailShape shape) {
-        return EXITS.get(shape);
+        return (Pair) AbstractMinecart.EXITS.get(shape);
     }
 
     @Override
@@ -227,11 +291,19 @@
 
     @Override
     protected double getDefaultGravity() {
-        return this.isInWater() ? 0.005 : 0.04;
+        return this.isInWater() ? 0.005D : 0.04D;
     }
 
     @Override
     public void tick() {
+        // CraftBukkit start
+        double prevX = this.getX();
+        double prevY = this.getY();
+        double prevZ = this.getZ();
+        float prevYaw = this.getYRot();
+        float prevPitch = this.getXRot();
+        // CraftBukkit end
+
         if (this.getHurtTime() > 0) {
             this.setHurtTime(this.getHurtTime() - 1);
         }
@@ -241,31 +313,34 @@
         }
 
         this.checkBelowWorld();
-        this.handlePortal();
+        // this.handlePortal(); // CraftBukkit - handled in postTick
         if (this.level().isClientSide) {
             if (this.lerpSteps > 0) {
                 this.lerpPositionAndRotationStep(this.lerpSteps, this.lerpX, this.lerpY, this.lerpZ, this.lerpYRot, this.lerpXRot);
-                this.lerpSteps--;
+                --this.lerpSteps;
             } else {
                 this.reapplyPosition();
                 this.setRot(this.getYRot(), this.getXRot());
             }
+
         } else {
             this.applyGravity();
             int i = Mth.floor(this.getX());
             int j = Mth.floor(this.getY());
             int k = Mth.floor(this.getZ());
+
             if (this.level().getBlockState(new BlockPos(i, j - 1, k)).is(BlockTags.RAILS)) {
-                j--;
+                --j;
             }
 
-            BlockPos blockPos = new BlockPos(i, j, k);
-            BlockState blockState = this.level().getBlockState(blockPos);
-            this.onRails = BaseRailBlock.isRail(blockState);
+            BlockPos blockposition = new BlockPos(i, j, k);
+            BlockState iblockdata = this.level().getBlockState(blockposition);
+
+            this.onRails = BaseRailBlock.isRail(iblockdata);
             if (this.onRails) {
-                this.moveAlongTrack(blockPos, blockState);
-                if (blockState.is(Blocks.ACTIVATOR_RAIL)) {
-                    this.activateMinecart(i, j, k, blockState.getValue(PoweredRailBlock.POWERED));
+                this.moveAlongTrack(blockposition, iblockdata);
+                if (iblockdata.is(Blocks.ACTIVATOR_RAIL)) {
+                    this.activateMinecart(i, j, k, (Boolean) iblockdata.getValue(PoweredRailBlock.POWERED));
                 }
             } else {
                 this.comeOffTrack();
@@ -273,41 +348,86 @@
 
             this.checkInsideBlocks();
             this.setXRot(0.0F);
-            double d = this.xo - this.getX();
-            double e = this.zo - this.getZ();
-            if (d * d + e * e > 0.001) {
-                this.setYRot((float)(Mth.atan2(e, d) * 180.0 / Math.PI));
+            double d0 = this.xo - this.getX();
+            double d1 = this.zo - this.getZ();
+
+            if (d0 * d0 + d1 * d1 > 0.001D) {
+                this.setYRot((float) (Mth.atan2(d1, d0) * 180.0D / Math.PI));
                 if (this.flipped) {
                     this.setYRot(this.getYRot() + 180.0F);
                 }
             }
 
-            double f = (double)Mth.wrapDegrees(this.getYRot() - this.yRotO);
-            if (f < -170.0 || f >= 170.0) {
+            double d2 = (double) Mth.wrapDegrees(this.getYRot() - this.yRotO);
+
+            if (d2 < -170.0D || d2 >= 170.0D) {
                 this.setYRot(this.getYRot() + 180.0F);
                 this.flipped = !this.flipped;
             }
 
             this.setRot(this.getYRot(), this.getXRot());
-            if (this.getMinecartType() == AbstractMinecart.Type.RIDEABLE && this.getDeltaMovement().horizontalDistanceSqr() > 0.01) {
-                List<Entity> list = this.level().getEntities(this, this.getBoundingBox().inflate(0.2F, 0.0, 0.2F), EntitySelector.pushableBy(this));
+            // CraftBukkit start
+            org.bukkit.World bworld = this.level().getWorld();
+            Location from = new Location(bworld, prevX, prevY, prevZ, prevYaw, prevPitch);
+            Location to = CraftLocation.toBukkit(this.position(), bworld, this.getYRot(), this.getXRot());
+            Vehicle vehicle = (Vehicle) this.getBukkitEntity();
+
+            this.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle));
+
+            if (!from.equals(to)) {
+                this.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, from, to));
+            }
+            // CraftBukkit end
+            if (this.getMinecartType() == AbstractMinecart.Type.RIDEABLE && this.getDeltaMovement().horizontalDistanceSqr() > 0.01D) {
+                List<Entity> list = this.level().getEntities((Entity) this, this.getBoundingBox().inflate(0.20000000298023224D, 0.0D, 0.20000000298023224D), EntitySelector.pushableBy(this));
+
                 if (!list.isEmpty()) {
-                    for (Entity entity : list) {
-                        if (!(entity instanceof Player)
-                            && !(entity instanceof IronGolem)
-                            && !(entity instanceof AbstractMinecart)
-                            && !this.isVehicle()
-                            && !entity.isPassenger()) {
+                    Iterator iterator = list.iterator();
+
+                    while (iterator.hasNext()) {
+                        Entity entity = (Entity) iterator.next();
+
+                        if (!(entity instanceof Player) && !(entity instanceof IronGolem) && !(entity instanceof AbstractMinecart) && !this.isVehicle() && !entity.isPassenger()) {
+                            // CraftBukkit start
+                            VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent(vehicle, entity.getBukkitEntity());
+                            this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
+
+                            if (collisionEvent.isCancelled()) {
+                                continue;
+                            }
+                            // CraftBukkit end
                             entity.startRiding(this);
                         } else {
-                            entity.push(this);
+                            // CraftBukkit start
+                            if (!this.isPassengerOfSameVehicle(entity)) {
+                                VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent(vehicle, entity.getBukkitEntity());
+                                this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
+
+                                if (collisionEvent.isCancelled()) {
+                                    continue;
+                                }
+                            }
+                            // CraftBukkit end
+                            entity.push((Entity) this);
                         }
                     }
                 }
             } else {
-                for (Entity entity2 : this.level().getEntities(this, this.getBoundingBox().inflate(0.2F, 0.0, 0.2F))) {
-                    if (!this.hasPassenger(entity2) && entity2.isPushable() && entity2 instanceof AbstractMinecart) {
-                        entity2.push(this);
+                Iterator iterator1 = this.level().getEntities(this, this.getBoundingBox().inflate(0.20000000298023224D, 0.0D, 0.20000000298023224D)).iterator();
+
+                while (iterator1.hasNext()) {
+                    Entity entity1 = (Entity) iterator1.next();
+
+                    if (!this.hasPassenger(entity1) && entity1.isPushable() && entity1 instanceof AbstractMinecart) {
+                        // CraftBukkit start
+                        VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent(vehicle, entity1.getBukkitEntity());
+                        this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
+
+                        if (collisionEvent.isCancelled()) {
+                            continue;
+                        }
+                        // CraftBukkit end
+                        entity1.push((Entity) this);
                     }
                 }
             }
@@ -323,183 +443,210 @@
     }
 
     protected double getMaxSpeed() {
-        return (this.isInWater() ? 4.0 : 8.0) / 20.0;
+        return (this.isInWater() ? this.maxSpeed / 2.0D: this.maxSpeed); // CraftBukkit
     }
 
-    public void activateMinecart(int x, int y, int z, boolean powered) {
-    }
+    public void activateMinecart(int x, int y, int z, boolean powered) {}
 
     protected void comeOffTrack() {
-        double d = this.getMaxSpeed();
-        Vec3 vec3 = this.getDeltaMovement();
-        this.setDeltaMovement(Mth.clamp(vec3.x, -d, d), vec3.y, Mth.clamp(vec3.z, -d, d));
+        double d0 = this.getMaxSpeed();
+        Vec3 vec3d = this.getDeltaMovement();
+
+        this.setDeltaMovement(Mth.clamp(vec3d.x, -d0, d0), vec3d.y, Mth.clamp(vec3d.z, -d0, d0));
         if (this.onGround()) {
-            this.setDeltaMovement(this.getDeltaMovement().scale(0.5));
+            // CraftBukkit start - replace magic numbers with our variables
+            this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.derailedX, this.getDeltaMovement().y * this.derailedY, this.getDeltaMovement().z * this.derailedZ));
+            // CraftBukkit end
         }
 
         this.move(MoverType.SELF, this.getDeltaMovement());
         if (!this.onGround()) {
-            this.setDeltaMovement(this.getDeltaMovement().scale(0.95));
+            // CraftBukkit start - replace magic numbers with our variables
+            this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.flyingX, this.getDeltaMovement().y * this.flyingY, this.getDeltaMovement().z * this.flyingZ));
+            // CraftBukkit end
         }
+
     }
 
     protected void moveAlongTrack(BlockPos pos, BlockState state) {
         this.resetFallDistance();
-        double d = this.getX();
-        double e = this.getY();
-        double f = this.getZ();
-        Vec3 vec3 = this.getPos(d, e, f);
-        e = (double)pos.getY();
-        boolean bl = false;
-        boolean bl2 = false;
+        double d0 = this.getX();
+        double d1 = this.getY();
+        double d2 = this.getZ();
+        Vec3 vec3d = this.getPos(d0, d1, d2);
+
+        d1 = (double) pos.getY();
+        boolean flag = false;
+        boolean flag1 = false;
+
         if (state.is(Blocks.POWERED_RAIL)) {
-            bl = state.getValue(PoweredRailBlock.POWERED);
-            bl2 = !bl;
+            flag = (Boolean) state.getValue(PoweredRailBlock.POWERED);
+            flag1 = !flag;
         }
 
-        double g = 0.0078125;
+        double d3 = 0.0078125D;
+
         if (this.isInWater()) {
-            g *= 0.2;
+            d3 *= 0.2D;
         }
 
-        Vec3 vec32 = this.getDeltaMovement();
-        RailShape railShape = state.getValue(((BaseRailBlock)state.getBlock()).getShapeProperty());
-        switch (railShape) {
+        Vec3 vec3d1 = this.getDeltaMovement();
+        RailShape blockpropertytrackposition = (RailShape) state.getValue(((BaseRailBlock) state.getBlock()).getShapeProperty());
+
+        switch (blockpropertytrackposition) {
             case ASCENDING_EAST:
-                this.setDeltaMovement(vec32.add(-g, 0.0, 0.0));
-                e++;
+                this.setDeltaMovement(vec3d1.add(-d3, 0.0D, 0.0D));
+                ++d1;
                 break;
             case ASCENDING_WEST:
-                this.setDeltaMovement(vec32.add(g, 0.0, 0.0));
-                e++;
+                this.setDeltaMovement(vec3d1.add(d3, 0.0D, 0.0D));
+                ++d1;
                 break;
             case ASCENDING_NORTH:
-                this.setDeltaMovement(vec32.add(0.0, 0.0, g));
-                e++;
+                this.setDeltaMovement(vec3d1.add(0.0D, 0.0D, d3));
+                ++d1;
                 break;
             case ASCENDING_SOUTH:
-                this.setDeltaMovement(vec32.add(0.0, 0.0, -g));
-                e++;
+                this.setDeltaMovement(vec3d1.add(0.0D, 0.0D, -d3));
+                ++d1;
         }
 
-        vec32 = this.getDeltaMovement();
-        Pair<Vec3i, Vec3i> pair = exits(railShape);
-        Vec3i vec3i = pair.getFirst();
-        Vec3i vec3i2 = pair.getSecond();
-        double h = (double)(vec3i2.getX() - vec3i.getX());
-        double i = (double)(vec3i2.getZ() - vec3i.getZ());
-        double j = Math.sqrt(h * h + i * i);
-        double k = vec32.x * h + vec32.z * i;
-        if (k < 0.0) {
-            h = -h;
-            i = -i;
+        vec3d1 = this.getDeltaMovement();
+        Pair<Vec3i, Vec3i> pair = AbstractMinecart.exits(blockpropertytrackposition);
+        Vec3i baseblockposition = (Vec3i) pair.getFirst();
+        Vec3i baseblockposition1 = (Vec3i) pair.getSecond();
+        double d4 = (double) (baseblockposition1.getX() - baseblockposition.getX());
+        double d5 = (double) (baseblockposition1.getZ() - baseblockposition.getZ());
+        double d6 = Math.sqrt(d4 * d4 + d5 * d5);
+        double d7 = vec3d1.x * d4 + vec3d1.z * d5;
+
+        if (d7 < 0.0D) {
+            d4 = -d4;
+            d5 = -d5;
         }
 
-        double l = Math.min(2.0, vec32.horizontalDistance());
-        vec32 = new Vec3(l * h / j, vec32.y, l * i / j);
-        this.setDeltaMovement(vec32);
+        double d8 = Math.min(2.0D, vec3d1.horizontalDistance());
+
+        vec3d1 = new Vec3(d8 * d4 / d6, vec3d1.y, d8 * d5 / d6);
+        this.setDeltaMovement(vec3d1);
         Entity entity = this.getFirstPassenger();
+
         if (entity instanceof Player) {
-            Vec3 vec33 = entity.getDeltaMovement();
-            double m = vec33.horizontalDistanceSqr();
-            double n = this.getDeltaMovement().horizontalDistanceSqr();
-            if (m > 1.0E-4 && n < 0.01) {
-                this.setDeltaMovement(this.getDeltaMovement().add(vec33.x * 0.1, 0.0, vec33.z * 0.1));
-                bl2 = false;
+            Vec3 vec3d2 = entity.getDeltaMovement();
+            double d9 = vec3d2.horizontalDistanceSqr();
+            double d10 = this.getDeltaMovement().horizontalDistanceSqr();
+
+            if (d9 > 1.0E-4D && d10 < 0.01D) {
+                this.setDeltaMovement(this.getDeltaMovement().add(vec3d2.x * 0.1D, 0.0D, vec3d2.z * 0.1D));
+                flag1 = false;
             }
         }
 
-        if (bl2) {
-            double o = this.getDeltaMovement().horizontalDistance();
-            if (o < 0.03) {
+        double d11;
+
+        if (flag1) {
+            d11 = this.getDeltaMovement().horizontalDistance();
+            if (d11 < 0.03D) {
                 this.setDeltaMovement(Vec3.ZERO);
             } else {
-                this.setDeltaMovement(this.getDeltaMovement().multiply(0.5, 0.0, 0.5));
+                this.setDeltaMovement(this.getDeltaMovement().multiply(0.5D, 0.0D, 0.5D));
             }
         }
 
-        double p = (double)pos.getX() + 0.5 + (double)vec3i.getX() * 0.5;
-        double q = (double)pos.getZ() + 0.5 + (double)vec3i.getZ() * 0.5;
-        double r = (double)pos.getX() + 0.5 + (double)vec3i2.getX() * 0.5;
-        double s = (double)pos.getZ() + 0.5 + (double)vec3i2.getZ() * 0.5;
-        h = r - p;
-        i = s - q;
-        double t;
-        if (h == 0.0) {
-            t = f - (double)pos.getZ();
-        } else if (i == 0.0) {
-            t = d - (double)pos.getX();
+        d11 = (double) pos.getX() + 0.5D + (double) baseblockposition.getX() * 0.5D;
+        double d12 = (double) pos.getZ() + 0.5D + (double) baseblockposition.getZ() * 0.5D;
+        double d13 = (double) pos.getX() + 0.5D + (double) baseblockposition1.getX() * 0.5D;
+        double d14 = (double) pos.getZ() + 0.5D + (double) baseblockposition1.getZ() * 0.5D;
+
+        d4 = d13 - d11;
+        d5 = d14 - d12;
+        double d15;
+        double d16;
+        double d17;
+
+        if (d4 == 0.0D) {
+            d15 = d2 - (double) pos.getZ();
+        } else if (d5 == 0.0D) {
+            d15 = d0 - (double) pos.getX();
         } else {
-            double v = d - p;
-            double w = f - q;
-            t = (v * h + w * i) * 2.0;
+            d16 = d0 - d11;
+            d17 = d2 - d12;
+            d15 = (d16 * d4 + d17 * d5) * 2.0D;
         }
 
-        d = p + h * t;
-        f = q + i * t;
-        this.setPos(d, e, f);
-        double y = this.isVehicle() ? 0.75 : 1.0;
-        double z = this.getMaxSpeed();
-        vec32 = this.getDeltaMovement();
-        this.move(MoverType.SELF, new Vec3(Mth.clamp(y * vec32.x, -z, z), 0.0, Mth.clamp(y * vec32.z, -z, z)));
-        if (vec3i.getY() != 0 && Mth.floor(this.getX()) - pos.getX() == vec3i.getX() && Mth.floor(this.getZ()) - pos.getZ() == vec3i.getZ()) {
-            this.setPos(this.getX(), this.getY() + (double)vec3i.getY(), this.getZ());
-        } else if (vec3i2.getY() != 0 && Mth.floor(this.getX()) - pos.getX() == vec3i2.getX() && Mth.floor(this.getZ()) - pos.getZ() == vec3i2.getZ()) {
-            this.setPos(this.getX(), this.getY() + (double)vec3i2.getY(), this.getZ());
+        d0 = d11 + d4 * d15;
+        d2 = d12 + d5 * d15;
+        this.setPos(d0, d1, d2);
+        d16 = this.isVehicle() ? 0.75D : 1.0D;
+        d17 = this.getMaxSpeed();
+        vec3d1 = this.getDeltaMovement();
+        this.move(MoverType.SELF, new Vec3(Mth.clamp(d16 * vec3d1.x, -d17, d17), 0.0D, Mth.clamp(d16 * vec3d1.z, -d17, d17)));
+        if (baseblockposition.getY() != 0 && Mth.floor(this.getX()) - pos.getX() == baseblockposition.getX() && Mth.floor(this.getZ()) - pos.getZ() == baseblockposition.getZ()) {
+            this.setPos(this.getX(), this.getY() + (double) baseblockposition.getY(), this.getZ());
+        } else if (baseblockposition1.getY() != 0 && Mth.floor(this.getX()) - pos.getX() == baseblockposition1.getX() && Mth.floor(this.getZ()) - pos.getZ() == baseblockposition1.getZ()) {
+            this.setPos(this.getX(), this.getY() + (double) baseblockposition1.getY(), this.getZ());
         }
 
         this.applyNaturalSlowdown();
-        Vec3 vec34 = this.getPos(this.getX(), this.getY(), this.getZ());
-        if (vec34 != null && vec3 != null) {
-            double aa = (vec3.y - vec34.y) * 0.05;
-            Vec3 vec35 = this.getDeltaMovement();
-            double ab = vec35.horizontalDistance();
-            if (ab > 0.0) {
-                this.setDeltaMovement(vec35.multiply((ab + aa) / ab, 1.0, (ab + aa) / ab));
+        Vec3 vec3d3 = this.getPos(this.getX(), this.getY(), this.getZ());
+        Vec3 vec3d4;
+        double d18;
+
+        if (vec3d3 != null && vec3d != null) {
+            double d19 = (vec3d.y - vec3d3.y) * 0.05D;
+
+            vec3d4 = this.getDeltaMovement();
+            d18 = vec3d4.horizontalDistance();
+            if (d18 > 0.0D) {
+                this.setDeltaMovement(vec3d4.multiply((d18 + d19) / d18, 1.0D, (d18 + d19) / d18));
             }
 
-            this.setPos(this.getX(), vec34.y, this.getZ());
+            this.setPos(this.getX(), vec3d3.y, this.getZ());
         }
 
-        int ac = Mth.floor(this.getX());
-        int ad = Mth.floor(this.getZ());
-        if (ac != pos.getX() || ad != pos.getZ()) {
-            Vec3 vec36 = this.getDeltaMovement();
-            double ae = vec36.horizontalDistance();
-            this.setDeltaMovement(ae * (double)(ac - pos.getX()), vec36.y, ae * (double)(ad - pos.getZ()));
+        int i = Mth.floor(this.getX());
+        int j = Mth.floor(this.getZ());
+
+        if (i != pos.getX() || j != pos.getZ()) {
+            vec3d4 = this.getDeltaMovement();
+            d18 = vec3d4.horizontalDistance();
+            this.setDeltaMovement(d18 * (double) (i - pos.getX()), vec3d4.y, d18 * (double) (j - pos.getZ()));
         }
 
-        if (bl) {
-            Vec3 vec37 = this.getDeltaMovement();
-            double af = vec37.horizontalDistance();
-            if (af > 0.01) {
-                double ag = 0.06;
-                this.setDeltaMovement(vec37.add(vec37.x / af * 0.06, 0.0, vec37.z / af * 0.06));
+        if (flag) {
+            vec3d4 = this.getDeltaMovement();
+            d18 = vec3d4.horizontalDistance();
+            if (d18 > 0.01D) {
+                double d20 = 0.06D;
+
+                this.setDeltaMovement(vec3d4.add(vec3d4.x / d18 * 0.06D, 0.0D, vec3d4.z / d18 * 0.06D));
             } else {
-                Vec3 vec38 = this.getDeltaMovement();
-                double ah = vec38.x;
-                double ai = vec38.z;
-                if (railShape == RailShape.EAST_WEST) {
+                Vec3 vec3d5 = this.getDeltaMovement();
+                double d21 = vec3d5.x;
+                double d22 = vec3d5.z;
+
+                if (blockpropertytrackposition == RailShape.EAST_WEST) {
                     if (this.isRedstoneConductor(pos.west())) {
-                        ah = 0.02;
+                        d21 = 0.02D;
                     } else if (this.isRedstoneConductor(pos.east())) {
-                        ah = -0.02;
+                        d21 = -0.02D;
                     }
                 } else {
-                    if (railShape != RailShape.NORTH_SOUTH) {
+                    if (blockpropertytrackposition != RailShape.NORTH_SOUTH) {
                         return;
                     }
 
                     if (this.isRedstoneConductor(pos.north())) {
-                        ai = 0.02;
+                        d22 = 0.02D;
                     } else if (this.isRedstoneConductor(pos.south())) {
-                        ai = -0.02;
+                        d22 = -0.02D;
                     }
                 }
 
-                this.setDeltaMovement(ah, vec38.y, ai);
+                this.setDeltaMovement(d21, vec3d5.y, d22);
             }
         }
+
     }
 
     @Override
@@ -512,14 +659,15 @@
     }
 
     protected void applyNaturalSlowdown() {
-        double d = this.isVehicle() ? 0.997 : 0.96;
-        Vec3 vec3 = this.getDeltaMovement();
-        vec3 = vec3.multiply(d, 0.0, d);
+        double d0 = this.isVehicle() || !this.slowWhenEmpty ? 0.997D : 0.96D; // CraftBukkit - add !this.slowWhenEmpty
+        Vec3 vec3d = this.getDeltaMovement();
+
+        vec3d = vec3d.multiply(d0, 0.0D, d0);
         if (this.isInWater()) {
-            vec3 = vec3.scale(0.95F);
+            vec3d = vec3d.scale(0.949999988079071D);
         }
 
-        this.setDeltaMovement(vec3);
+        this.setDeltaMovement(vec3d);
     }
 
     @Nullable
@@ -527,32 +675,36 @@
         int i = Mth.floor(x);
         int j = Mth.floor(y);
         int k = Mth.floor(z);
+
         if (this.level().getBlockState(new BlockPos(i, j - 1, k)).is(BlockTags.RAILS)) {
-            j--;
+            --j;
         }
 
-        BlockState blockState = this.level().getBlockState(new BlockPos(i, j, k));
-        if (BaseRailBlock.isRail(blockState)) {
-            RailShape railShape = blockState.getValue(((BaseRailBlock)blockState.getBlock()).getShapeProperty());
-            y = (double)j;
-            if (railShape.isAscending()) {
-                y = (double)(j + 1);
+        BlockState iblockdata = this.level().getBlockState(new BlockPos(i, j, k));
+
+        if (BaseRailBlock.isRail(iblockdata)) {
+            RailShape blockpropertytrackposition = (RailShape) iblockdata.getValue(((BaseRailBlock) iblockdata.getBlock()).getShapeProperty());
+
+            y = (double) j;
+            if (blockpropertytrackposition.isAscending()) {
+                y = (double) (j + 1);
             }
 
-            Pair<Vec3i, Vec3i> pair = exits(railShape);
-            Vec3i vec3i = pair.getFirst();
-            Vec3i vec3i2 = pair.getSecond();
-            double d = (double)(vec3i2.getX() - vec3i.getX());
-            double e = (double)(vec3i2.getZ() - vec3i.getZ());
-            double f = Math.sqrt(d * d + e * e);
-            d /= f;
-            e /= f;
-            x += d * offset;
-            z += e * offset;
-            if (vec3i.getY() != 0 && Mth.floor(x) - i == vec3i.getX() && Mth.floor(z) - k == vec3i.getZ()) {
-                y += (double)vec3i.getY();
-            } else if (vec3i2.getY() != 0 && Mth.floor(x) - i == vec3i2.getX() && Mth.floor(z) - k == vec3i2.getZ()) {
-                y += (double)vec3i2.getY();
+            Pair<Vec3i, Vec3i> pair = AbstractMinecart.exits(blockpropertytrackposition);
+            Vec3i baseblockposition = (Vec3i) pair.getFirst();
+            Vec3i baseblockposition1 = (Vec3i) pair.getSecond();
+            double d4 = (double) (baseblockposition1.getX() - baseblockposition.getX());
+            double d5 = (double) (baseblockposition1.getZ() - baseblockposition.getZ());
+            double d6 = Math.sqrt(d4 * d4 + d5 * d5);
+
+            d4 /= d6;
+            d5 /= d6;
+            x += d4 * offset;
+            z += d5 * offset;
+            if (baseblockposition.getY() != 0 && Mth.floor(x) - i == baseblockposition.getX() && Mth.floor(z) - k == baseblockposition.getZ()) {
+                y += (double) baseblockposition.getY();
+            } else if (baseblockposition1.getY() != 0 && Mth.floor(x) - i == baseblockposition1.getX() && Mth.floor(z) - k == baseblockposition1.getZ()) {
+                y += (double) baseblockposition1.getY();
             }
 
             return this.getPos(x, y, z);
@@ -566,43 +718,47 @@
         int i = Mth.floor(x);
         int j = Mth.floor(y);
         int k = Mth.floor(z);
+
         if (this.level().getBlockState(new BlockPos(i, j - 1, k)).is(BlockTags.RAILS)) {
-            j--;
+            --j;
         }
 
-        BlockState blockState = this.level().getBlockState(new BlockPos(i, j, k));
-        if (BaseRailBlock.isRail(blockState)) {
-            RailShape railShape = blockState.getValue(((BaseRailBlock)blockState.getBlock()).getShapeProperty());
-            Pair<Vec3i, Vec3i> pair = exits(railShape);
-            Vec3i vec3i = pair.getFirst();
-            Vec3i vec3i2 = pair.getSecond();
-            double d = (double)i + 0.5 + (double)vec3i.getX() * 0.5;
-            double e = (double)j + 0.0625 + (double)vec3i.getY() * 0.5;
-            double f = (double)k + 0.5 + (double)vec3i.getZ() * 0.5;
-            double g = (double)i + 0.5 + (double)vec3i2.getX() * 0.5;
-            double h = (double)j + 0.0625 + (double)vec3i2.getY() * 0.5;
-            double l = (double)k + 0.5 + (double)vec3i2.getZ() * 0.5;
-            double m = g - d;
-            double n = (h - e) * 2.0;
-            double o = l - f;
-            double p;
-            if (m == 0.0) {
-                p = z - (double)k;
-            } else if (o == 0.0) {
-                p = x - (double)i;
+        BlockState iblockdata = this.level().getBlockState(new BlockPos(i, j, k));
+
+        if (BaseRailBlock.isRail(iblockdata)) {
+            RailShape blockpropertytrackposition = (RailShape) iblockdata.getValue(((BaseRailBlock) iblockdata.getBlock()).getShapeProperty());
+            Pair<Vec3i, Vec3i> pair = AbstractMinecart.exits(blockpropertytrackposition);
+            Vec3i baseblockposition = (Vec3i) pair.getFirst();
+            Vec3i baseblockposition1 = (Vec3i) pair.getSecond();
+            double d3 = (double) i + 0.5D + (double) baseblockposition.getX() * 0.5D;
+            double d4 = (double) j + 0.0625D + (double) baseblockposition.getY() * 0.5D;
+            double d5 = (double) k + 0.5D + (double) baseblockposition.getZ() * 0.5D;
+            double d6 = (double) i + 0.5D + (double) baseblockposition1.getX() * 0.5D;
+            double d7 = (double) j + 0.0625D + (double) baseblockposition1.getY() * 0.5D;
+            double d8 = (double) k + 0.5D + (double) baseblockposition1.getZ() * 0.5D;
+            double d9 = d6 - d3;
+            double d10 = (d7 - d4) * 2.0D;
+            double d11 = d8 - d5;
+            double d12;
+
+            if (d9 == 0.0D) {
+                d12 = z - (double) k;
+            } else if (d11 == 0.0D) {
+                d12 = x - (double) i;
             } else {
-                double r = x - d;
-                double s = z - f;
-                p = (r * m + s * o) * 2.0;
+                double d13 = x - d3;
+                double d14 = z - d5;
+
+                d12 = (d13 * d9 + d14 * d11) * 2.0D;
             }
 
-            x = d + m * p;
-            y = e + n * p;
-            z = f + o * p;
-            if (n < 0.0) {
-                y++;
-            } else if (n > 0.0) {
-                y += 0.5;
+            x = d3 + d9 * d12;
+            y = d4 + d10 * d12;
+            z = d5 + d11 * d12;
+            if (d10 < 0.0D) {
+                ++y;
+            } else if (d10 > 0.0D) {
+                y += 0.5D;
             }
 
             return new Vec3(x, y, z);
@@ -613,8 +769,9 @@
 
     @Override
     public AABB getBoundingBoxForCulling() {
-        AABB aABB = this.getBoundingBox();
-        return this.hasCustomDisplay() ? aABB.inflate((double)Math.abs(this.getDisplayOffset()) / 16.0) : aABB;
+        AABB axisalignedbb = this.getBoundingBox();
+
+        return this.hasCustomDisplay() ? axisalignedbb.inflate((double) Math.abs(this.getDisplayOffset()) / 16.0D) : axisalignedbb;
     }
 
     @Override
@@ -623,6 +780,7 @@
             this.setDisplayBlockState(NbtUtils.readBlockState(this.level().holderLookup(Registries.BLOCK), nbt.getCompound("DisplayState")));
             this.setDisplayOffset(nbt.getInt("DisplayOffset"));
         }
+
     }
 
     @Override
@@ -632,71 +790,80 @@
             nbt.put("DisplayState", NbtUtils.writeBlockState(this.getDisplayBlockState()));
             nbt.putInt("DisplayOffset", this.getDisplayOffset());
         }
+
     }
 
     @Override
     public void push(Entity entity) {
         if (!this.level().isClientSide) {
             if (!entity.noPhysics && !this.noPhysics) {
+                if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant
                 if (!this.hasPassenger(entity)) {
-                    double d = entity.getX() - this.getX();
-                    double e = entity.getZ() - this.getZ();
-                    double f = d * d + e * e;
-                    if (f >= 1.0E-4F) {
-                        f = Math.sqrt(f);
-                        d /= f;
-                        e /= f;
-                        double g = 1.0 / f;
-                        if (g > 1.0) {
-                            g = 1.0;
+                    // CraftBukkit start
+                    VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity());
+                    this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
+
+                    if (collisionEvent.isCancelled()) {
+                        return;
+                    }
+                    // CraftBukkit end
+                    double d0 = entity.getX() - this.getX();
+                    double d1 = entity.getZ() - this.getZ();
+                    double d2 = d0 * d0 + d1 * d1;
+
+                    if (d2 >= 9.999999747378752E-5D) {
+                        d2 = Math.sqrt(d2);
+                        d0 /= d2;
+                        d1 /= d2;
+                        double d3 = 1.0D / d2;
+
+                        if (d3 > 1.0D) {
+                            d3 = 1.0D;
                         }
 
-                        d *= g;
-                        e *= g;
-                        d *= 0.1F;
-                        e *= 0.1F;
-                        d *= 0.5;
-                        e *= 0.5;
+                        d0 *= d3;
+                        d1 *= d3;
+                        d0 *= 0.10000000149011612D;
+                        d1 *= 0.10000000149011612D;
+                        d0 *= 0.5D;
+                        d1 *= 0.5D;
                         if (entity instanceof AbstractMinecart) {
-                            double h = entity.getX() - this.getX();
-                            double i = entity.getZ() - this.getZ();
-                            Vec3 vec3 = new Vec3(h, 0.0, i).normalize();
-                            Vec3 vec32 = new Vec3(
-                                    (double)Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)),
-                                    0.0,
-                                    (double)Mth.sin(this.getYRot() * (float) (Math.PI / 180.0))
-                                )
-                                .normalize();
-                            double j = Math.abs(vec3.dot(vec32));
-                            if (j < 0.8F) {
+                            double d4 = entity.getX() - this.getX();
+                            double d5 = entity.getZ() - this.getZ();
+                            Vec3 vec3d = (new Vec3(d4, 0.0D, d5)).normalize();
+                            Vec3 vec3d1 = (new Vec3((double) Mth.cos(this.getYRot() * 0.017453292F), 0.0D, (double) Mth.sin(this.getYRot() * 0.017453292F))).normalize();
+                            double d6 = Math.abs(vec3d.dot(vec3d1));
+
+                            if (d6 < 0.800000011920929D) {
                                 return;
                             }
 
-                            Vec3 vec33 = this.getDeltaMovement();
-                            Vec3 vec34 = entity.getDeltaMovement();
-                            if (((AbstractMinecart)entity).getMinecartType() == AbstractMinecart.Type.FURNACE
-                                && this.getMinecartType() != AbstractMinecart.Type.FURNACE) {
-                                this.setDeltaMovement(vec33.multiply(0.2, 1.0, 0.2));
-                                this.push(vec34.x - d, 0.0, vec34.z - e);
-                                entity.setDeltaMovement(vec34.multiply(0.95, 1.0, 0.95));
-                            } else if (((AbstractMinecart)entity).getMinecartType() != AbstractMinecart.Type.FURNACE
-                                && this.getMinecartType() == AbstractMinecart.Type.FURNACE) {
-                                entity.setDeltaMovement(vec34.multiply(0.2, 1.0, 0.2));
-                                entity.push(vec33.x + d, 0.0, vec33.z + e);
-                                this.setDeltaMovement(vec33.multiply(0.95, 1.0, 0.95));
+                            Vec3 vec3d2 = this.getDeltaMovement();
+                            Vec3 vec3d3 = entity.getDeltaMovement();
+
+                            if (((AbstractMinecart) entity).getMinecartType() == AbstractMinecart.Type.FURNACE && this.getMinecartType() != AbstractMinecart.Type.FURNACE) {
+                                this.setDeltaMovement(vec3d2.multiply(0.2D, 1.0D, 0.2D));
+                                this.push(vec3d3.x - d0, 0.0D, vec3d3.z - d1);
+                                entity.setDeltaMovement(vec3d3.multiply(0.95D, 1.0D, 0.95D));
+                            } else if (((AbstractMinecart) entity).getMinecartType() != AbstractMinecart.Type.FURNACE && this.getMinecartType() == AbstractMinecart.Type.FURNACE) {
+                                entity.setDeltaMovement(vec3d3.multiply(0.2D, 1.0D, 0.2D));
+                                entity.push(vec3d2.x + d0, 0.0D, vec3d2.z + d1);
+                                this.setDeltaMovement(vec3d2.multiply(0.95D, 1.0D, 0.95D));
                             } else {
-                                double k = (vec34.x + vec33.x) / 2.0;
-                                double l = (vec34.z + vec33.z) / 2.0;
-                                this.setDeltaMovement(vec33.multiply(0.2, 1.0, 0.2));
-                                this.push(k - d, 0.0, l - e);
-                                entity.setDeltaMovement(vec34.multiply(0.2, 1.0, 0.2));
-                                entity.push(k + d, 0.0, l + e);
+                                double d7 = (vec3d3.x + vec3d2.x) / 2.0D;
+                                double d8 = (vec3d3.z + vec3d2.z) / 2.0D;
+
+                                this.setDeltaMovement(vec3d2.multiply(0.2D, 1.0D, 0.2D));
+                                this.push(d7 - d0, 0.0D, d8 - d1);
+                                entity.setDeltaMovement(vec3d3.multiply(0.2D, 1.0D, 0.2D));
+                                entity.push(d7 + d0, 0.0D, d8 + d1);
                             }
                         } else {
-                            this.push(-d, 0.0, -e);
-                            entity.push(d / 4.0, 0.0, e / 4.0);
+                            this.push(-d0, 0.0D, -d1);
+                            entity.push(d0 / 4.0D, 0.0D, d1 / 4.0D);
                         }
                     }
+
                 }
             }
         }
@@ -707,8 +874,8 @@
         this.lerpX = x;
         this.lerpY = y;
         this.lerpZ = z;
-        this.lerpYRot = (double)yaw;
-        this.lerpXRot = (double)pitch;
+        this.lerpYRot = (double) yaw;
+        this.lerpXRot = (double) pitch;
         this.lerpSteps = interpolationSteps + 2;
         this.setDeltaMovement(this.targetDeltaMovement);
     }
@@ -730,12 +897,12 @@
 
     @Override
     public float lerpTargetXRot() {
-        return this.lerpSteps > 0 ? (float)this.lerpXRot : this.getXRot();
+        return this.lerpSteps > 0 ? (float) this.lerpXRot : this.getXRot();
     }
 
     @Override
     public float lerpTargetYRot() {
-        return this.lerpSteps > 0 ? (float)this.lerpYRot : this.getYRot();
+        return this.lerpSteps > 0 ? (float) this.lerpYRot : this.getYRot();
     }
 
     @Override
@@ -747,7 +914,7 @@
     public abstract AbstractMinecart.Type getMinecartType();
 
     public BlockState getDisplayBlockState() {
-        return !this.hasCustomDisplay() ? this.getDefaultDisplayBlockState() : Block.stateById(this.getEntityData().get(DATA_ID_DISPLAY_BLOCK));
+        return !this.hasCustomDisplay() ? this.getDefaultDisplayBlockState() : Block.stateById((Integer) this.getEntityData().get(AbstractMinecart.DATA_ID_DISPLAY_BLOCK));
     }
 
     public BlockState getDefaultDisplayBlockState() {
@@ -755,7 +922,7 @@
     }
 
     public int getDisplayOffset() {
-        return !this.hasCustomDisplay() ? this.getDefaultDisplayOffset() : this.getEntityData().get(DATA_ID_DISPLAY_OFFSET);
+        return !this.hasCustomDisplay() ? this.getDefaultDisplayOffset() : (Integer) this.getEntityData().get(AbstractMinecart.DATA_ID_DISPLAY_OFFSET);
     }
 
     public int getDefaultDisplayOffset() {
@@ -763,42 +930,77 @@
     }
 
     public void setDisplayBlockState(BlockState state) {
-        this.getEntityData().set(DATA_ID_DISPLAY_BLOCK, Block.getId(state));
+        this.getEntityData().set(AbstractMinecart.DATA_ID_DISPLAY_BLOCK, Block.getId(state));
         this.setCustomDisplay(true);
     }
 
     public void setDisplayOffset(int offset) {
-        this.getEntityData().set(DATA_ID_DISPLAY_OFFSET, offset);
+        this.getEntityData().set(AbstractMinecart.DATA_ID_DISPLAY_OFFSET, offset);
         this.setCustomDisplay(true);
     }
 
     public boolean hasCustomDisplay() {
-        return this.getEntityData().get(DATA_ID_CUSTOM_DISPLAY);
+        return (Boolean) this.getEntityData().get(AbstractMinecart.DATA_ID_CUSTOM_DISPLAY);
     }
 
     public void setCustomDisplay(boolean present) {
-        this.getEntityData().set(DATA_ID_CUSTOM_DISPLAY, present);
+        this.getEntityData().set(AbstractMinecart.DATA_ID_CUSTOM_DISPLAY, present);
     }
 
     @Override
     public ItemStack getPickResult() {
-        return new ItemStack(switch (this.getMinecartType()) {
-            case CHEST -> Items.CHEST_MINECART;
-            case FURNACE -> Items.FURNACE_MINECART;
-            case TNT -> Items.TNT_MINECART;
-            default -> Items.MINECART;
-            case HOPPER -> Items.HOPPER_MINECART;
-            case COMMAND_BLOCK -> Items.COMMAND_BLOCK_MINECART;
-        });
+        Item item;
+
+        switch (this.getMinecartType().ordinal()) {
+            case 1:
+                item = Items.CHEST_MINECART;
+                break;
+            case 2:
+                item = Items.FURNACE_MINECART;
+                break;
+            case 3:
+                item = Items.TNT_MINECART;
+                break;
+            case 4:
+            default:
+                item = Items.MINECART;
+                break;
+            case 5:
+                item = Items.HOPPER_MINECART;
+                break;
+            case 6:
+                item = Items.COMMAND_BLOCK_MINECART;
+        }
+
+        return new ItemStack(item);
     }
 
     public static enum Type {
-        RIDEABLE,
-        CHEST,
-        FURNACE,
-        TNT,
-        SPAWNER,
-        HOPPER,
-        COMMAND_BLOCK;
+
+        RIDEABLE, CHEST, FURNACE, TNT, SPAWNER, HOPPER, COMMAND_BLOCK;
+
+        private Type() {}
+    }
+
+    // CraftBukkit start - Methods for getting and setting flying and derailed velocity modifiers
+    public Vector getFlyingVelocityMod() {
+        return new Vector(this.flyingX, this.flyingY, this.flyingZ);
+    }
+
+    public void setFlyingVelocityMod(Vector flying) {
+        this.flyingX = flying.getX();
+        this.flyingY = flying.getY();
+        this.flyingZ = flying.getZ();
+    }
+
+    public Vector getDerailedVelocityMod() {
+        return new Vector(this.derailedX, this.derailedY, this.derailedZ);
     }
+
+    public void setDerailedVelocityMod(Vector derailed) {
+        this.derailedX = derailed.getX();
+        this.derailedY = derailed.getY();
+        this.derailedZ = derailed.getZ();
+    }
+    // CraftBukkit end
 }
