--- 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;
@@ -44,8 +46,17 @@
 import net.minecraft.world.level.gameevent.GameEvent;
 import net.minecraft.world.phys.AABB;
 import net.minecraft.world.phys.Vec3;
+// CraftBukkit start
+import org.bukkit.Location;
+import org.bukkit.entity.Vehicle;
+import org.bukkit.event.vehicle.VehicleDamageEvent;
+import org.bukkit.event.vehicle.VehicleDestroyEvent;
+import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
+import org.bukkit.util.Vector;
+// CraftBukkit end
 
 public abstract class AbstractMinecart extends Entity {
+
     private static final EntityDataAccessor<Integer> DATA_ID_HURT = SynchedEntityData.defineId(AbstractMinecart.class, EntityDataSerializers.INT);
     private static final EntityDataAccessor<Integer> DATA_ID_HURTDIR = SynchedEntityData.defineId(AbstractMinecart.class, EntityDataSerializers.INT);
     private static final EntityDataAccessor<Float> DATA_ID_DAMAGE = SynchedEntityData.defineId(AbstractMinecart.class, EntityDataSerializers.FLOAT);
@@ -55,25 +66,26 @@
     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 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 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));
     });
     private int lSteps;
     private double lx;
@@ -85,6 +97,17 @@
     private double lyd;
     private double lzd;
 
+    // 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.949999988079071D; // Paper - restore vanilla precision
+    private double flyingY = 0.949999988079071D; // Paper - restore vanilla precision
+    private double flyingZ = 0.949999988079071D; // Paper - restore vanilla precision
+    public double maxSpeed = 0.4D;
+    // CraftBukkit end
+
     protected AbstractMinecart(EntityType<?> type, Level world) {
         super(type, world);
         this.blocksBuilding = true;
@@ -99,19 +122,7 @@
     }
 
     public static AbstractMinecart createMinecart(Level world, double x, double y, double z, AbstractMinecart.Type type) {
-        if (type == AbstractMinecart.Type.CHEST) {
-            return new MinecartChest(world, x, y, z);
-        } else if (type == AbstractMinecart.Type.FURNACE) {
-            return new MinecartFurnace(world, x, y, z);
-        } else if (type == AbstractMinecart.Type.TNT) {
-            return new MinecartTNT(world, x, y, z);
-        } else if (type == AbstractMinecart.Type.SPAWNER) {
-            return new MinecartSpawner(world, x, y, z);
-        } else if (type == AbstractMinecart.Type.HOPPER) {
-            return new MinecartHopper(world, x, y, z);
-        } else {
-            return (AbstractMinecart)(type == AbstractMinecart.Type.COMMAND_BLOCK ? new MinecartCommandBlock(world, x, y, z) : new Minecart(world, x, y, z));
-        }
+        return (AbstractMinecart) (type == AbstractMinecart.Type.CHEST ? new MinecartChest(world, x, y, z) : (type == AbstractMinecart.Type.FURNACE ? new MinecartFurnace(world, x, y, z) : (type == AbstractMinecart.Type.TNT ? new MinecartTNT(world, x, y, z) : (type == AbstractMinecart.Type.SPAWNER ? new MinecartSpawner(world, x, y, z) : (type == AbstractMinecart.Type.HOPPER ? new MinecartHopper(world, x, y, z) : (type == AbstractMinecart.Type.COMMAND_BLOCK ? new MinecartCommandBlock(world, x, y, z) : new Minecart(world, x, y, z)))))));
     }
 
     @Override
@@ -121,21 +132,29 @@
 
     @Override
     protected void defineSynchedData() {
-        this.entityData.define(DATA_ID_HURT, 0);
-        this.entityData.define(DATA_ID_HURTDIR, 1);
-        this.entityData.define(DATA_ID_DAMAGE, 0.0F);
-        this.entityData.define(DATA_ID_DISPLAY_BLOCK, Block.getId(Blocks.AIR.defaultBlockState()));
-        this.entityData.define(DATA_ID_DISPLAY_OFFSET, 6);
-        this.entityData.define(DATA_ID_CUSTOM_DISPLAY, false);
+        this.entityData.define(AbstractMinecart.DATA_ID_HURT, 0);
+        this.entityData.define(AbstractMinecart.DATA_ID_HURTDIR, 1);
+        this.entityData.define(AbstractMinecart.DATA_ID_DAMAGE, 0.0F);
+        this.entityData.define(AbstractMinecart.DATA_ID_DISPLAY_BLOCK, Block.getId(Blocks.AIR.defaultBlockState()));
+        this.entityData.define(AbstractMinecart.DATA_ID_DISPLAY_OFFSET, 6);
+        this.entityData.define(AbstractMinecart.DATA_ID_CUSTOM_DISPLAY, false);
     }
 
     @Override
     public boolean canCollideWith(Entity other) {
-        return Boat.canVehicleCollide(this, other);
+        // Paper start - fixed 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
     }
 
     @Override
-    public boolean isPushable() {
+    public boolean isCollidable(boolean ignoreClimbing) { // Paper
         return true;
     }
 
@@ -151,48 +170,64 @@
 
     @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), () -> {
-                            return DismountHelper.nonClimbableShape(this.level, mutableBlockPos.below());
+                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(d)) {
-                            AABB aABB = new AABB((double)(-f), 0.0D, (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;
+
+                        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) -> {
-                    return this.level.getBlockState(pos).getCollisionShape(this.level, pos);
+            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 (e + g <= h) {
-                    passenger.setPose(pose2);
+
+                if (d1 + d2 <= d3) {
+                    passenger.setPose(entitypose1);
                     break;
                 }
             }
@@ -207,15 +242,38 @@
             if (this.isInvulnerableTo(source)) {
                 return false;
             } else {
+                // CraftBukkit start - fire VehicleDamageEvent
+                Vehicle vehicle = (Vehicle) this.getBukkitEntity();
+                org.bukkit.entity.Entity passenger = (source.getEntity() == null) ? null : source.getEntity().getBukkitEntity();
+
+                VehicleDamageEvent event = new VehicleDamageEvent(vehicle, passenger, amount);
+                this.level.getCraftServer().getPluginManager().callEvent(event);
+
+                if (event.isCancelled()) {
+                    return false;
+                }
+
+                amount = (float) event.getDamage();
+                // CraftBukkit end
                 this.setHurtDir(-this.getHurtDir());
                 this.setHurtTime(10);
                 this.markHurt();
                 this.setDamage(this.getDamage() + amount * 10.0F);
                 this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity());
-                boolean bl = source.getEntity() instanceof Player && ((Player)source.getEntity()).getAbilities().instabuild;
-                if (bl || this.getDamage() > 40.0F) {
+                boolean flag = source.getEntity() instanceof Player && ((Player) source.getEntity()).getAbilities().instabuild;
+
+                if (flag || this.getDamage() > 40.0F) {
+                    // CraftBukkit start
+                    VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, passenger);
+                    this.level.getCraftServer().getPluginManager().callEvent(destroyEvent);
+
+                    if (destroyEvent.isCancelled()) {
+                        this.setDamage(40); // Maximize damage so this doesn't get triggered again right away
+                        return true;
+                    }
+                    // CraftBukkit end
                     this.ejectPassengers();
-                    if (bl && !this.hasCustomName()) {
+                    if (flag && !this.hasCustomName()) {
                         this.discard();
                     } else {
                         this.destroy(source);
@@ -231,19 +289,21 @@
 
     @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();
     }
 
     public void destroy(DamageSource damageSource) {
         this.kill();
         if (this.level.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
-            ItemStack itemStack = new ItemStack(this.getDropItem());
+            ItemStack itemstack = new ItemStack(this.getDropItem());
+
             if (this.hasCustomName()) {
-                itemStack.setHoverName(this.getCustomName());
+                itemstack.setHoverName(this.getCustomName());
             }
 
-            this.spawnAtLocation(itemStack);
+            this.spawnAtLocation(itemstack);
         }
 
     }
@@ -263,7 +323,7 @@
     }
 
     private static Pair<Vec3i, Vec3i> exits(RailShape shape) {
-        return EXITS.get(shape);
+        return (Pair) AbstractMinecart.EXITS.get(shape);
     }
 
     @Override
@@ -273,6 +333,14 @@
 
     @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);
         }
@@ -282,17 +350,20 @@
         }
 
         this.checkOutOfWorld();
-        this.handleNetherPortal();
+        // this.handleNetherPortal(); // CraftBukkit - handled in postTick
+        double d0;
+
         if (this.level.isClientSide) {
             if (this.lSteps > 0) {
-                double d = this.getX() + (this.lx - this.getX()) / (double)this.lSteps;
-                double e = this.getY() + (this.ly - this.getY()) / (double)this.lSteps;
-                double f = this.getZ() + (this.lz - this.getZ()) / (double)this.lSteps;
-                double g = Mth.wrapDegrees(this.lyr - (double)this.getYRot());
-                this.setYRot(this.getYRot() + (float)g / (float)this.lSteps);
-                this.setXRot(this.getXRot() + (float)(this.lxr - (double)this.getXRot()) / (float)this.lSteps);
+                d0 = this.getX() + (this.lx - this.getX()) / (double) this.lSteps;
+                double d1 = this.getY() + (this.ly - this.getY()) / (double) this.lSteps;
+                double d2 = this.getZ() + (this.lz - this.getZ()) / (double) this.lSteps;
+                double d3 = Mth.wrapDegrees(this.lyr - (double) this.getYRot());
+
+                this.setYRot(this.getYRot() + (float) d3 / (float) this.lSteps);
+                this.setXRot(this.getXRot() + (float) (this.lxr - (double) this.getXRot()) / (float) this.lSteps);
                 --this.lSteps;
-                this.setPos(d, e, f);
+                this.setPos(d0, d1, d2);
                 this.setRot(this.getYRot(), this.getXRot());
             } else {
                 this.reapplyPosition();
@@ -301,23 +372,25 @@
 
         } else {
             if (!this.isNoGravity()) {
-                double h = this.isInWater() ? -0.005D : -0.04D;
-                this.setDeltaMovement(this.getDeltaMovement().add(0.0D, h, 0.0D));
+                d0 = this.isInWater() ? -0.005D : -0.04D;
+                this.setDeltaMovement(this.getDeltaMovement().add(0.0D, d0, 0.0D));
             }
 
             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;
             }
 
-            BlockPos blockPos = new BlockPos(i, j, k);
-            BlockState blockState = this.level.getBlockState(blockPos);
-            if (BaseRailBlock.isRail(blockState)) {
-                this.moveAlongTrack(blockPos, blockState);
-                if (blockState.is(Blocks.ACTIVATOR_RAIL)) {
-                    this.activateMinecart(i, j, k, blockState.getValue(PoweredRailBlock.POWERED));
+            BlockPos blockposition = new BlockPos(i, j, k);
+            BlockState iblockdata = this.level.getBlockState(blockposition);
+
+            if (BaseRailBlock.isRail(iblockdata)) {
+                this.moveAlongTrack(blockposition, iblockdata);
+                if (iblockdata.is(Blocks.ACTIVATOR_RAIL)) {
+                    this.activateMinecart(i, j, k, (Boolean) iblockdata.getValue(PoweredRailBlock.POWERED));
                 }
             } else {
                 this.comeOffTrack();
@@ -325,38 +398,84 @@
 
             this.checkInsideBlocks();
             this.setXRot(0.0F);
-            double l = this.xo - this.getX();
-            double m = this.zo - this.getZ();
-            if (l * l + m * m > 0.001D) {
-                this.setYRot((float)(Mth.atan2(m, l) * 180.0D / Math.PI));
+            double d4 = this.xo - this.getX();
+            double d5 = this.zo - this.getZ();
+
+            if (d4 * d4 + d5 * d5 > 0.001D) {
+                this.setYRot((float) (Mth.atan2(d5, d4) * 180.0D / 3.141592653589793D));
                 if (this.flipped) {
                     this.setYRot(this.getYRot() + 180.0F);
                 }
             }
 
-            double n = (double)Mth.wrapDegrees(this.getYRot() - this.yRotO);
-            if (n < -170.0D || n >= 170.0D) {
+            double d6 = (double) Mth.wrapDegrees(this.getYRot() - this.yRotO);
+
+            if (d6 < -170.0D || d6 >= 170.0D) {
                 this.setYRot(this.getYRot() + 180.0F);
                 this.flipped = !this.flipped;
             }
 
             this.setRot(this.getYRot(), this.getXRot());
+            // CraftBukkit start
+            org.bukkit.World bworld = this.level.getWorld();
+            Location from = new Location(bworld, prevX, prevY, prevZ, prevYaw, prevPitch);
+            Location to = new Location(bworld, this.getX(), this.getY(), this.getZ(), 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(this, this.getBoundingBox().inflate((double)0.2F, 0.0D, (double)0.2F), EntitySelector.pushableBy(this));
+                List<Entity> list = this.level.getEntities((Entity) this, this.getBoundingBox().inflate(0.20000000298023224D, 0.0D, 0.20000000298023224D), EntitySelector.pushableBy(this));
+
                 if (!list.isEmpty()) {
-                    for(int o = 0; o < list.size(); ++o) {
-                        Entity entity = list.get(o);
+                    for (int l = 0; l < list.size(); ++l) {
+                        Entity entity = (Entity) list.get(l);
+
                         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 {
+                            // 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(this);
                         }
                     }
                 }
             } else {
-                for(Entity entity2 : this.level.getEntities(this, this.getBoundingBox().inflate((double)0.2F, 0.0D, (double)0.2F))) {
-                    if (!this.hasPassenger(entity2) && entity2.isPushable() && entity2 instanceof AbstractMinecart) {
-                        entity2.push(this);
+                Iterator iterator = this.level.getEntities(this, this.getBoundingBox().inflate(0.20000000298023224D, 0.0D, 0.20000000298023224D)).iterator();
+
+                while (iterator.hasNext()) {
+                    Entity entity1 = (Entity) iterator.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(this);
                     }
                 }
             }
@@ -372,182 +491,207 @@
     }
 
     protected double getMaxSpeed() {
-        return (this.isInWater() ? 4.0D : 8.0D) / 20.0D;
+        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.5D));
+            // 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.95D));
+            // 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.0078125D;
+        double d3 = 0.0078125D;
+
         if (this.isInWater()) {
-            g *= 0.2D;
+            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.0D, 0.0D));
-                ++e;
+                this.setDeltaMovement(vec3d1.add(-d3, 0.0D, 0.0D));
+                ++d1;
                 break;
             case ASCENDING_WEST:
-                this.setDeltaMovement(vec32.add(g, 0.0D, 0.0D));
-                ++e;
+                this.setDeltaMovement(vec3d1.add(d3, 0.0D, 0.0D));
+                ++d1;
                 break;
             case ASCENDING_NORTH:
-                this.setDeltaMovement(vec32.add(0.0D, 0.0D, g));
-                ++e;
+                this.setDeltaMovement(vec3d1.add(0.0D, 0.0D, d3));
+                ++d1;
                 break;
             case ASCENDING_SOUTH:
-                this.setDeltaMovement(vec32.add(0.0D, 0.0D, -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.0D) {
-            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.0D, 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-4D && n < 0.01D) {
-                this.setDeltaMovement(this.getDeltaMovement().add(vec33.x * 0.1D, 0.0D, vec33.z * 0.1D));
-                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.03D) {
+        double d11;
+
+        if (flag1) {
+            d11 = this.getDeltaMovement().horizontalDistance();
+            if (d11 < 0.03D) {
                 this.setDeltaMovement(Vec3.ZERO);
             } else {
                 this.setDeltaMovement(this.getDeltaMovement().multiply(0.5D, 0.0D, 0.5D));
             }
         }
 
-        double p = (double)pos.getX() + 0.5D + (double)vec3i.getX() * 0.5D;
-        double q = (double)pos.getZ() + 0.5D + (double)vec3i.getZ() * 0.5D;
-        double r = (double)pos.getX() + 0.5D + (double)vec3i2.getX() * 0.5D;
-        double s = (double)pos.getZ() + 0.5D + (double)vec3i2.getZ() * 0.5D;
-        h = r - p;
-        i = s - q;
-        double t;
-        if (h == 0.0D) {
-            t = f - (double)pos.getZ();
-        } else if (i == 0.0D) {
-            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.0D;
+            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.75D : 1.0D;
-        double z = this.getMaxSpeed();
-        vec32 = this.getDeltaMovement();
-        this.move(MoverType.SELF, new Vec3(Mth.clamp(y * vec32.x, -z, z), 0.0D, 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.05D;
-            Vec3 vec35 = this.getDeltaMovement();
-            double ab = vec35.horizontalDistance();
-            if (ab > 0.0D) {
-                this.setDeltaMovement(vec35.multiply((ab + aa) / ab, 1.0D, (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.01D) {
-                double ag = 0.06D;
-                this.setDeltaMovement(vec37.add(vec37.x / af * 0.06D, 0.0D, vec37.z / af * 0.06D));
+        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.02D;
+                        d21 = 0.02D;
                     } else if (this.isRedstoneConductor(pos.east())) {
-                        ah = -0.02D;
+                        d21 = -0.02D;
                     }
                 } else {
-                    if (railShape != RailShape.NORTH_SOUTH) {
+                    if (blockpropertytrackposition != RailShape.NORTH_SOUTH) {
                         return;
                     }
 
                     if (this.isRedstoneConductor(pos.north())) {
-                        ai = 0.02D;
+                        d22 = 0.02D;
                     } else if (this.isRedstoneConductor(pos.south())) {
-                        ai = -0.02D;
+                        d22 = -0.02D;
                     }
                 }
 
-                this.setDeltaMovement(ah, vec38.y, ai);
+                this.setDeltaMovement(d21, vec3d5.y, d22);
             }
         }
 
@@ -558,14 +702,15 @@
     }
 
     protected void applyNaturalSlowdown() {
-        double d = this.isVehicle() ? 0.997D : 0.96D;
-        Vec3 vec3 = this.getDeltaMovement();
-        vec3 = vec3.multiply(d, 0.0D, 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((double)0.95F);
+            vec3d = vec3d.scale(0.949999988079071D);
         }
 
-        this.setDeltaMovement(vec3);
+        this.setDeltaMovement(vec3d);
     }
 
     @Nullable
@@ -573,32 +718,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;
         }
 
-        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);
@@ -612,42 +761,46 @@
         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;
         }
 
-        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.5D + (double)vec3i.getX() * 0.5D;
-            double e = (double)j + 0.0625D + (double)vec3i.getY() * 0.5D;
-            double f = (double)k + 0.5D + (double)vec3i.getZ() * 0.5D;
-            double g = (double)i + 0.5D + (double)vec3i2.getX() * 0.5D;
-            double h = (double)j + 0.0625D + (double)vec3i2.getY() * 0.5D;
-            double l = (double)k + 0.5D + (double)vec3i2.getZ() * 0.5D;
-            double m = g - d;
-            double n = (h - e) * 2.0D;
-            double o = l - f;
-            double p;
-            if (m == 0.0D) {
-                p = z - (double)k;
-            } else if (o == 0.0D) {
-                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.0D;
+                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.0D) {
+            x = d3 + d9 * d12;
+            y = d4 + d10 * d12;
+            z = d5 + d11 * d12;
+            if (d10 < 0.0D) {
                 ++y;
-            } else if (n > 0.0D) {
+            } else if (d10 > 0.0D) {
                 y += 0.5D;
             }
 
@@ -659,8 +812,9 @@
 
     @Override
     public AABB getBoundingBoxForCulling() {
-        AABB aABB = this.getBoundingBox();
-        return this.hasCustomDisplay() ? aABB.inflate((double)Math.abs(this.getDisplayOffset()) / 16.0D) : aABB;
+        AABB axisalignedbb = this.getBoundingBox();
+
+        return this.hasCustomDisplay() ? axisalignedbb.inflate((double) Math.abs(this.getDisplayOffset()) / 16.0D) : axisalignedbb;
     }
 
     @Override
@@ -686,56 +840,70 @@
     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
                 if (!this.hasPassenger(entity)) {
-                    double d = entity.getX() - this.getX();
-                    double e = entity.getZ() - this.getZ();
-                    double f = d * d + e * e;
-                    if (f >= (double)1.0E-4F) {
-                        f = Math.sqrt(f);
-                        d /= f;
-                        e /= f;
-                        double g = 1.0D / f;
-                        if (g > 1.0D) {
-                            g = 1.0D;
+                    // 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 *= (double)0.1F;
-                        e *= (double)0.1F;
-                        d *= 0.5D;
-                        e *= 0.5D;
+                        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.0D, i)).normalize();
-                            Vec3 vec32 = (new Vec3((double)Mth.cos(this.getYRot() * ((float)Math.PI / 180F)), 0.0D, (double)Mth.sin(this.getYRot() * ((float)Math.PI / 180F)))).normalize();
-                            double j = Math.abs(vec3.dot(vec32));
-                            if (j < (double)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.2D, 1.0D, 0.2D));
-                                this.push(vec34.x - d, 0.0D, vec34.z - e);
-                                entity.setDeltaMovement(vec34.multiply(0.95D, 1.0D, 0.95D));
-                            } else if (((AbstractMinecart)entity).getMinecartType() != AbstractMinecart.Type.FURNACE && this.getMinecartType() == AbstractMinecart.Type.FURNACE) {
-                                entity.setDeltaMovement(vec34.multiply(0.2D, 1.0D, 0.2D));
-                                entity.push(vec33.x + d, 0.0D, vec33.z + e);
-                                this.setDeltaMovement(vec33.multiply(0.95D, 1.0D, 0.95D));
+                            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.0D;
-                                double l = (vec34.z + vec33.z) / 2.0D;
-                                this.setDeltaMovement(vec33.multiply(0.2D, 1.0D, 0.2D));
-                                this.push(k - d, 0.0D, l - e);
-                                entity.setDeltaMovement(vec34.multiply(0.2D, 1.0D, 0.2D));
-                                entity.push(k + d, 0.0D, 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.0D, -e);
-                            entity.push(d / 4.0D, 0.0D, e / 4.0D);
+                            this.push(-d0, 0.0D, -d1);
+                            entity.push(d0 / 4.0D, 0.0D, d1 / 4.0D);
                         }
                     }
 
@@ -749,8 +917,8 @@
         this.lx = x;
         this.ly = y;
         this.lz = z;
-        this.lyr = (double)yaw;
-        this.lxr = (double)pitch;
+        this.lyr = (double) yaw;
+        this.lxr = (double) pitch;
         this.lSteps = interpolationSteps + 2;
         this.setDeltaMovement(this.lxd, this.lyd, this.lzd);
     }
@@ -764,33 +932,33 @@
     }
 
     public void setDamage(float damageWobbleStrength) {
-        this.entityData.set(DATA_ID_DAMAGE, damageWobbleStrength);
+        this.entityData.set(AbstractMinecart.DATA_ID_DAMAGE, damageWobbleStrength);
     }
 
     public float getDamage() {
-        return this.entityData.get(DATA_ID_DAMAGE);
+        return (Float) this.entityData.get(AbstractMinecart.DATA_ID_DAMAGE);
     }
 
     public void setHurtTime(int wobbleTicks) {
-        this.entityData.set(DATA_ID_HURT, wobbleTicks);
+        this.entityData.set(AbstractMinecart.DATA_ID_HURT, wobbleTicks);
     }
 
     public int getHurtTime() {
-        return this.entityData.get(DATA_ID_HURT);
+        return (Integer) this.entityData.get(AbstractMinecart.DATA_ID_HURT);
     }
 
     public void setHurtDir(int wobbleSide) {
-        this.entityData.set(DATA_ID_HURTDIR, wobbleSide);
+        this.entityData.set(AbstractMinecart.DATA_ID_HURTDIR, wobbleSide);
     }
 
     public int getHurtDir() {
-        return this.entityData.get(DATA_ID_HURTDIR);
+        return (Integer) this.entityData.get(AbstractMinecart.DATA_ID_HURTDIR);
     }
 
     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() {
@@ -798,7 +966,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() {
@@ -806,26 +974,27 @@
     }
 
     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() {
         Item item;
+
         switch (this.getMinecartType()) {
             case FURNACE:
                 item = Items.FURNACE_MINECART;
@@ -850,12 +1019,31 @@
     }
 
     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
 }
