--- a/net/minecraft/world/entity/vehicle/Boat.java
+++ b/net/minecraft/world/entity/vehicle/Boat.java
@@ -1,6 +1,8 @@
 package net.minecraft.world.entity.vehicle;
 
 import com.google.common.collect.Lists;
+import com.google.common.collect.UnmodifiableIterator;
+import java.util.Iterator;
 import java.util.List;
 import java.util.function.IntFunction;
 import javax.annotation.Nullable;
@@ -38,6 +40,7 @@
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.Items;
 import net.minecraft.world.level.GameRules;
+import net.minecraft.world.level.ItemLike;
 import net.minecraft.world.level.Level;
 import net.minecraft.world.level.block.Block;
 import net.minecraft.world.level.block.Blocks;
@@ -50,8 +53,17 @@
 import net.minecraft.world.phys.shapes.BooleanOp;
 import net.minecraft.world.phys.shapes.Shapes;
 import net.minecraft.world.phys.shapes.VoxelShape;
+// CraftBukkit start
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.util.CraftLocation;
+import org.bukkit.entity.Vehicle;
+import org.bukkit.event.vehicle.VehicleDestroyEvent;
+import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
+import org.bukkit.event.vehicle.VehicleMoveEvent;
+// CraftBukkit end
 
 public class Boat extends VehicleEntity implements VariantHolder<Boat.Type> {
+
     private static final EntityDataAccessor<Integer> DATA_ID_TYPE = SynchedEntityData.defineId(Boat.class, EntityDataSerializers.INT);
     private static final EntityDataAccessor<Boolean> DATA_ID_PADDLE_LEFT = SynchedEntityData.defineId(Boat.class, EntityDataSerializers.BOOLEAN);
     private static final EntityDataAccessor<Boolean> DATA_ID_PADDLE_RIGHT = SynchedEntityData.defineId(Boat.class, EntityDataSerializers.BOOLEAN);
@@ -59,10 +71,10 @@
     public static final int PADDLE_LEFT = 0;
     public static final int PADDLE_RIGHT = 1;
     private static final int TIME_TO_EJECT = 60;
-    private static final float PADDLE_SPEED = (float) (Math.PI / 8);
-    public static final double PADDLE_SOUND_TIME = (float) (Math.PI / 4);
+    private static final float PADDLE_SPEED = 0.3926991F;
+    public static final double PADDLE_SOUND_TIME = 0.7853981852531433D;
     public static final int BUBBLE_TIME = 60;
-    private final float[] paddlePositions = new float[2];
+    private final float[] paddlePositions;
     private float invFriction;
     private float outOfControlTicks;
     private float deltaRotation;
@@ -87,8 +99,17 @@
     private float bubbleAngle;
     private float bubbleAngleO;
 
+    // CraftBukkit start
+    // PAIL: Some of these haven't worked since a few updates, and since 1.9 they are less and less applicable.
+    public double maxSpeed = 0.4D;
+    public double occupiedDeceleration = 0.2D;
+    public double unoccupiedDeceleration = -1;
+    public boolean landBoats = false;
+    // CraftBukkit end
+
     public Boat(EntityType<? extends Boat> type, Level world) {
         super(type, world);
+        this.paddlePositions = new float[2];
         this.blocksBuilding = true;
     }
 
@@ -108,15 +129,15 @@
     @Override
     protected void defineSynchedData(SynchedEntityData.Builder builder) {
         super.defineSynchedData(builder);
-        builder.define(DATA_ID_TYPE, Boat.Type.OAK.ordinal());
-        builder.define(DATA_ID_PADDLE_LEFT, false);
-        builder.define(DATA_ID_PADDLE_RIGHT, false);
-        builder.define(DATA_ID_BUBBLE_TIME, 0);
+        builder.define(Boat.DATA_ID_TYPE, Boat.Type.OAK.ordinal());
+        builder.define(Boat.DATA_ID_PADDLE_LEFT, false);
+        builder.define(Boat.DATA_ID_PADDLE_RIGHT, false);
+        builder.define(Boat.DATA_ID_BUBBLE_TIME, 0);
     }
 
     @Override
     public boolean canCollideWith(Entity other) {
-        return canVehicleCollide(this, other);
+        return Boat.canVehicleCollide(this, other);
     }
 
     public static boolean canVehicleCollide(Entity entity, Entity other) {
@@ -129,7 +150,7 @@
     }
 
     @Override
-    public boolean isPushable() {
+    public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule
         return true;
     }
 
@@ -140,24 +161,23 @@
 
     @Override
     protected Vec3 getPassengerAttachmentPoint(Entity passenger, EntityDimensions dimensions, float scaleFactor) {
-        float f = this.getSinglePassengerXOffset();
+        float f1 = this.getSinglePassengerXOffset();
+
         if (this.getPassengers().size() > 1) {
             int i = this.getPassengers().indexOf(passenger);
+
             if (i == 0) {
-                f = 0.2F;
+                f1 = 0.2F;
             } else {
-                f = -0.6F;
+                f1 = -0.6F;
             }
 
             if (passenger instanceof Animal) {
-                f += 0.2F;
+                f1 += 0.2F;
             }
         }
 
-        return new Vec3(
-                0.0, this.getVariant() == Boat.Type.BAMBOO ? (double)(dimensions.height() * 0.8888889F) : (double)(dimensions.height() / 3.0F), (double)f
-            )
-            .yRot(-this.getYRot() * (float) (Math.PI / 180.0));
+        return (new Vec3(0.0D, this.getVariant() == Boat.Type.BAMBOO ? (double) (dimensions.height() * 0.8888889F) : (double) (dimensions.height() / 3.0F), (double) f1)).yRot(-this.getYRot() * 0.017453292F);
     }
 
     @Override
@@ -170,49 +190,81 @@
             }
         }
 
-        this.level()
-            .addParticle(
-                ParticleTypes.SPLASH,
-                this.getX() + (double)this.random.nextFloat(),
-                this.getY() + 0.7,
-                this.getZ() + (double)this.random.nextFloat(),
-                0.0,
-                0.0,
-                0.0
-            );
+        this.level().addParticle(ParticleTypes.SPLASH, this.getX() + (double) this.random.nextFloat(), this.getY() + 0.7D, this.getZ() + (double) this.random.nextFloat(), 0.0D, 0.0D, 0.0D);
         if (this.random.nextInt(20) == 0) {
-            this.level()
-                .playLocalSound(
-                    this.getX(), this.getY(), this.getZ(), this.getSwimSplashSound(), this.getSoundSource(), 1.0F, 0.8F + 0.4F * this.random.nextFloat(), false
-                );
+            this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), this.getSwimSplashSound(), this.getSoundSource(), 1.0F, 0.8F + 0.4F * this.random.nextFloat(), false);
             this.gameEvent(GameEvent.SPLASH, this.getControllingPassenger());
         }
+
     }
 
     @Override
     public void push(Entity entity) {
+        if (!this.level().paperConfig().collisions.allowVehicleCollisions && this.level().paperConfig().collisions.onlyPlayersCollide && !(entity instanceof Player)) return; // Paper - Collision option for requiring a player participant
         if (entity instanceof Boat) {
             if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) {
+                // CraftBukkit start
+                if (!this.isPassengerOfSameVehicle(entity)) {
+                    VehicleEntityCollisionEvent event = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity());
+                    this.level().getCraftServer().getPluginManager().callEvent(event);
+
+                    if (event.isCancelled()) {
+                        return;
+                    }
+                }
+                // CraftBukkit end
                 super.push(entity);
             }
         } else if (entity.getBoundingBox().minY <= this.getBoundingBox().minY) {
+            // CraftBukkit start
+            if (!this.isPassengerOfSameVehicle(entity)) {
+                VehicleEntityCollisionEvent event = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity());
+                this.level().getCraftServer().getPluginManager().callEvent(event);
+
+                if (event.isCancelled()) {
+                    return;
+                }
+            }
+            // CraftBukkit end
             super.push(entity);
         }
+
     }
 
     @Override
     public Item getDropItem() {
-        return switch (this.getVariant()) {
-            case SPRUCE -> Items.SPRUCE_BOAT;
-            case BIRCH -> Items.BIRCH_BOAT;
-            case JUNGLE -> Items.JUNGLE_BOAT;
-            case ACACIA -> Items.ACACIA_BOAT;
-            case CHERRY -> Items.CHERRY_BOAT;
-            case DARK_OAK -> Items.DARK_OAK_BOAT;
-            case MANGROVE -> Items.MANGROVE_BOAT;
-            case BAMBOO -> Items.BAMBOO_RAFT;
-            default -> Items.OAK_BOAT;
-        };
+        Item item;
+
+        switch (this.getVariant().ordinal()) {
+            case 1:
+                item = Items.SPRUCE_BOAT;
+                break;
+            case 2:
+                item = Items.BIRCH_BOAT;
+                break;
+            case 3:
+                item = Items.JUNGLE_BOAT;
+                break;
+            case 4:
+                item = Items.ACACIA_BOAT;
+                break;
+            case 5:
+                item = Items.CHERRY_BOAT;
+                break;
+            case 6:
+                item = Items.DARK_OAK_BOAT;
+                break;
+            case 7:
+                item = Items.MANGROVE_BOAT;
+                break;
+            case 8:
+                item = Items.BAMBOO_RAFT;
+                break;
+            default:
+                item = Items.OAK_BOAT;
+        }
+
+        return item;
     }
 
     @Override
@@ -232,8 +284,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 = 10;
     }
 
@@ -254,12 +306,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
@@ -267,6 +319,7 @@
         return this.getDirection().getClockWise();
     }
 
+    private Location lastLocation; // CraftBukkit
     @Override
     public void tick() {
         this.oldStatus = this.status;
@@ -274,7 +327,7 @@
         if (this.status != Boat.Status.UNDER_WATER && this.status != Boat.Status.UNDER_FLOWING_WATER) {
             this.outOfControlTicks = 0.0F;
         } else {
-            this.outOfControlTicks++;
+            ++this.outOfControlTicks;
         }
 
         if (!this.level().isClientSide && this.outOfControlTicks >= 60.0F) {
@@ -307,52 +360,56 @@
             this.setDeltaMovement(Vec3.ZERO);
         }
 
+        // CraftBukkit start
+        org.bukkit.Server server = this.level().getCraftServer();
+        org.bukkit.World bworld = this.level().getWorld();
+
+        Location to = CraftLocation.toBukkit(this.position(), bworld, this.getYRot(), this.getXRot());
+        Vehicle vehicle = (Vehicle) this.getBukkitEntity();
+
+        server.getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle));
+
+        if (this.lastLocation != null && !this.lastLocation.equals(to)) {
+            VehicleMoveEvent event = new VehicleMoveEvent(vehicle, this.lastLocation, to);
+            server.getPluginManager().callEvent(event);
+        }
+        this.lastLocation = vehicle.getLocation();
+        // CraftBukkit end
+
         this.tickBubbleColumn();
 
-        for (int i = 0; i <= 1; i++) {
+        for (int i = 0; i <= 1; ++i) {
             if (this.getPaddleState(i)) {
-                if (!this.isSilent()
-                    && (double)(this.paddlePositions[i] % (float) (Math.PI * 2)) <= (float) (Math.PI / 4)
-                    && (double)((this.paddlePositions[i] + (float) (Math.PI / 8)) % (float) (Math.PI * 2)) >= (float) (Math.PI / 4)) {
-                    SoundEvent soundEvent = this.getPaddleSound();
-                    if (soundEvent != null) {
-                        Vec3 vec3 = this.getViewVector(1.0F);
-                        double d = i == 1 ? -vec3.z : vec3.z;
-                        double e = i == 1 ? vec3.x : -vec3.x;
-                        this.level()
-                            .playSound(
-                                null,
-                                this.getX() + d,
-                                this.getY(),
-                                this.getZ() + e,
-                                soundEvent,
-                                this.getSoundSource(),
-                                1.0F,
-                                0.8F + 0.4F * this.random.nextFloat()
-                            );
+                if (!this.isSilent() && (double) (this.paddlePositions[i] % 6.2831855F) <= 0.7853981852531433D && (double) ((this.paddlePositions[i] + 0.3926991F) % 6.2831855F) >= 0.7853981852531433D) {
+                    SoundEvent soundeffect = this.getPaddleSound();
+
+                    if (soundeffect != null) {
+                        Vec3 vec3d = this.getViewVector(1.0F);
+                        double d0 = i == 1 ? -vec3d.z : vec3d.z;
+                        double d1 = i == 1 ? vec3d.x : -vec3d.x;
+
+                        this.level().playSound((Player) null, this.getX() + d0, this.getY(), this.getZ() + d1, soundeffect, this.getSoundSource(), 1.0F, 0.8F + 0.4F * this.random.nextFloat());
                     }
                 }
 
-                this.paddlePositions[i] = this.paddlePositions[i] + (float) (Math.PI / 8);
+                this.paddlePositions[i] += 0.3926991F;
             } else {
                 this.paddlePositions[i] = 0.0F;
             }
         }
 
         this.checkInsideBlocks();
-        List<Entity> list = this.level().getEntities(this, this.getBoundingBox().inflate(0.2F, -0.01F, 0.2F), EntitySelector.pushableBy(this));
+        List<Entity> list = this.level().getEntities((Entity) this, this.getBoundingBox().inflate(0.20000000298023224D, -0.009999999776482582D, 0.20000000298023224D), EntitySelector.pushableBy(this));
+
         if (!list.isEmpty()) {
-            boolean bl = !this.level().isClientSide && !(this.getControllingPassenger() instanceof Player);
+            boolean flag = !this.level().isClientSide && !(this.getControllingPassenger() instanceof Player);
+            Iterator iterator = list.iterator();
 
-            for (Entity entity : list) {
-                if (!entity.hasPassenger(this)) {
-                    if (bl
-                        && this.getPassengers().size() < this.getMaxPassengers()
-                        && !entity.isPassenger()
-                        && this.hasEnoughSpaceFor(entity)
-                        && entity instanceof LivingEntity
-                        && !(entity instanceof WaterAnimal)
-                        && !(entity instanceof Player)) {
+            while (iterator.hasNext()) {
+                Entity entity = (Entity) iterator.next();
+
+                if (!entity.hasPassenger((Entity) this)) {
+                    if (flag && this.getPassengers().size() < this.getMaxPassengers() && !entity.isPassenger() && this.hasEnoughSpaceFor(entity) && entity instanceof LivingEntity && !(entity instanceof WaterAnimal) && !(entity instanceof Player)) {
                         entity.startRiding(this);
                     } else {
                         this.push(entity);
@@ -360,11 +417,14 @@
                 }
             }
         }
+
     }
 
     private void tickBubbleColumn() {
+        int i;
+
         if (this.level().isClientSide) {
-            int i = this.getBubbleTime();
+            i = this.getBubbleTime();
             if (i > 0) {
                 this.bubbleMultiplier += 0.05F;
             } else {
@@ -373,42 +433,48 @@
 
             this.bubbleMultiplier = Mth.clamp(this.bubbleMultiplier, 0.0F, 1.0F);
             this.bubbleAngleO = this.bubbleAngle;
-            this.bubbleAngle = 10.0F * (float)Math.sin((double)(0.5F * (float)this.level().getGameTime())) * this.bubbleMultiplier;
+            this.bubbleAngle = 10.0F * (float) Math.sin((double) (0.5F * (float) this.level().getGameTime())) * this.bubbleMultiplier;
         } else {
             if (!this.isAboveBubbleColumn) {
                 this.setBubbleTime(0);
             }
 
-            int j = this.getBubbleTime();
-            if (j > 0) {
-                this.setBubbleTime(--j);
-                int k = 60 - j - 1;
-                if (k > 0 && j == 0) {
+            i = this.getBubbleTime();
+            if (i > 0) {
+                --i;
+                this.setBubbleTime(i);
+                int j = 60 - i - 1;
+
+                if (j > 0 && i == 0) {
                     this.setBubbleTime(0);
-                    Vec3 vec3 = this.getDeltaMovement();
+                    Vec3 vec3d = this.getDeltaMovement();
+
                     if (this.bubbleColumnDirectionIsDown) {
-                        this.setDeltaMovement(vec3.add(0.0, -0.7, 0.0));
+                        this.setDeltaMovement(vec3d.add(0.0D, -0.7D, 0.0D));
                         this.ejectPassengers();
                     } else {
-                        this.setDeltaMovement(vec3.x, this.hasPassenger(entity -> entity instanceof Player) ? 2.7 : 0.6, vec3.z);
+                        this.setDeltaMovement(vec3d.x, this.hasPassenger((entity) -> {
+                            return entity instanceof Player;
+                        }) ? 2.7D : 0.6D, vec3d.z);
                     }
                 }
 
                 this.isAboveBubbleColumn = false;
             }
         }
+
     }
 
     @Nullable
     protected SoundEvent getPaddleSound() {
-        switch (this.getStatus()) {
-            case IN_WATER:
-            case UNDER_WATER:
-            case UNDER_FLOWING_WATER:
+        switch (this.getStatus().ordinal()) {
+            case 0:
+            case 1:
+            case 2:
                 return SoundEvents.BOAT_PADDLE_WATER;
-            case ON_LAND:
+            case 3:
                 return SoundEvents.BOAT_PADDLE_LAND;
-            case IN_AIR:
+            case 4:
             default:
                 return null;
         }
@@ -422,30 +488,30 @@
 
         if (this.lerpSteps > 0) {
             this.lerpPositionAndRotationStep(this.lerpSteps, this.lerpX, this.lerpY, this.lerpZ, this.lerpYRot, this.lerpXRot);
-            this.lerpSteps--;
+            --this.lerpSteps;
         }
     }
 
     public void setPaddleState(boolean leftMoving, boolean rightMoving) {
-        this.entityData.set(DATA_ID_PADDLE_LEFT, leftMoving);
-        this.entityData.set(DATA_ID_PADDLE_RIGHT, rightMoving);
+        this.entityData.set(Boat.DATA_ID_PADDLE_LEFT, leftMoving);
+        this.entityData.set(Boat.DATA_ID_PADDLE_RIGHT, rightMoving);
     }
 
     public float getRowingTime(int paddle, float tickDelta) {
-        return this.getPaddleState(paddle)
-            ? Mth.clampedLerp(this.paddlePositions[paddle] - (float) (Math.PI / 8), this.paddlePositions[paddle], tickDelta)
-            : 0.0F;
+        return this.getPaddleState(paddle) ? Mth.clampedLerp(this.paddlePositions[paddle] - 0.3926991F, this.paddlePositions[paddle], tickDelta) : 0.0F;
     }
 
     public Boat.Status getStatus() {
-        Boat.Status status = this.isUnderwater();
-        if (status != null) {
+        Boat.Status entityboat_enumstatus = this.isUnderwater();
+
+        if (entityboat_enumstatus != null) {
             this.waterLevel = this.getBoundingBox().maxY;
-            return status;
+            return entityboat_enumstatus;
         } else if (this.checkInWater()) {
             return Boat.Status.IN_WATER;
         } else {
             float f = this.getGroundFriction();
+
             if (f > 0.0F) {
                 this.landFriction = f;
                 return Boat.Status.ON_LAND;
@@ -456,71 +522,83 @@
     }
 
     public float getWaterLevelAbove() {
-        AABB aABB = this.getBoundingBox();
-        int i = Mth.floor(aABB.minX);
-        int j = Mth.ceil(aABB.maxX);
-        int k = Mth.floor(aABB.maxY);
-        int l = Mth.ceil(aABB.maxY - this.lastYd);
-        int m = Mth.floor(aABB.minZ);
-        int n = Mth.ceil(aABB.maxZ);
-        BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+        AABB axisalignedbb = this.getBoundingBox();
+        int i = Mth.floor(axisalignedbb.minX);
+        int j = Mth.ceil(axisalignedbb.maxX);
+        int k = Mth.floor(axisalignedbb.maxY);
+        int l = Mth.ceil(axisalignedbb.maxY - this.lastYd);
+        int i1 = Mth.floor(axisalignedbb.minZ);
+        int j1 = Mth.ceil(axisalignedbb.maxZ);
+        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+        int k1 = k;
 
-        label39:
-        for (int o = k; o < l; o++) {
+        while (k1 < l) {
             float f = 0.0F;
+            int l1 = i;
 
-            for (int p = i; p < j; p++) {
-                for (int q = m; q < n; q++) {
-                    mutableBlockPos.set(p, o, q);
-                    FluidState fluidState = this.level().getFluidState(mutableBlockPos);
-                    if (fluidState.is(FluidTags.WATER)) {
-                        f = Math.max(f, fluidState.getHeight(this.level(), mutableBlockPos));
-                    }
+            label35:
+            while (true) {
+                if (l1 < j) {
+                    int i2 = i1;
 
-                    if (f >= 1.0F) {
-                        continue label39;
+                    while (true) {
+                        if (i2 >= j1) {
+                            ++l1;
+                            continue label35;
+                        }
+
+                        blockposition_mutableblockposition.set(l1, k1, i2);
+                        FluidState fluid = this.level().getFluidState(blockposition_mutableblockposition);
+
+                        if (fluid.is(FluidTags.WATER)) {
+                            f = Math.max(f, fluid.getHeight(this.level(), blockposition_mutableblockposition));
+                        }
+
+                        if (f >= 1.0F) {
+                            break;
+                        }
+
+                        ++i2;
                     }
+                } else if (f < 1.0F) {
+                    return (float) blockposition_mutableblockposition.getY() + f;
                 }
-            }
 
-            if (f < 1.0F) {
-                return (float)mutableBlockPos.getY() + f;
+                ++k1;
+                break;
             }
         }
 
-        return (float)(l + 1);
+        return (float) (l + 1);
     }
 
     public float getGroundFriction() {
-        AABB aABB = this.getBoundingBox();
-        AABB aABB2 = new AABB(aABB.minX, aABB.minY - 0.001, aABB.minZ, aABB.maxX, aABB.minY, aABB.maxZ);
-        int i = Mth.floor(aABB2.minX) - 1;
-        int j = Mth.ceil(aABB2.maxX) + 1;
-        int k = Mth.floor(aABB2.minY) - 1;
-        int l = Mth.ceil(aABB2.maxY) + 1;
-        int m = Mth.floor(aABB2.minZ) - 1;
-        int n = Mth.ceil(aABB2.maxZ) + 1;
-        VoxelShape voxelShape = Shapes.create(aABB2);
+        AABB axisalignedbb = this.getBoundingBox();
+        AABB axisalignedbb1 = new AABB(axisalignedbb.minX, axisalignedbb.minY - 0.001D, axisalignedbb.minZ, axisalignedbb.maxX, axisalignedbb.minY, axisalignedbb.maxZ);
+        int i = Mth.floor(axisalignedbb1.minX) - 1;
+        int j = Mth.ceil(axisalignedbb1.maxX) + 1;
+        int k = Mth.floor(axisalignedbb1.minY) - 1;
+        int l = Mth.ceil(axisalignedbb1.maxY) + 1;
+        int i1 = Mth.floor(axisalignedbb1.minZ) - 1;
+        int j1 = Mth.ceil(axisalignedbb1.maxZ) + 1;
+        VoxelShape voxelshape = Shapes.create(axisalignedbb1);
         float f = 0.0F;
-        int o = 0;
-        BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+        int k1 = 0;
+        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
 
-        for (int p = i; p < j; p++) {
-            for (int q = m; q < n; q++) {
-                int r = (p != i && p != j - 1 ? 0 : 1) + (q != m && q != n - 1 ? 0 : 1);
-                if (r != 2) {
-                    for (int s = k; s < l; s++) {
-                        if (r <= 0 || s != k && s != l - 1) {
-                            mutableBlockPos.set(p, s, q);
-                            BlockState blockState = this.level().getBlockState(mutableBlockPos);
-                            if (!(blockState.getBlock() instanceof WaterlilyBlock)
-                                && Shapes.joinIsNotEmpty(
-                                    blockState.getCollisionShape(this.level(), mutableBlockPos).move((double)p, (double)s, (double)q),
-                                    voxelShape,
-                                    BooleanOp.AND
-                                )) {
-                                f += blockState.getBlock().getFriction();
-                                o++;
+        for (int l1 = i; l1 < j; ++l1) {
+            for (int i2 = i1; i2 < j1; ++i2) {
+                int j2 = (l1 != i && l1 != j - 1 ? 0 : 1) + (i2 != i1 && i2 != j1 - 1 ? 0 : 1);
+
+                if (j2 != 2) {
+                    for (int k2 = k; k2 < l; ++k2) {
+                        if (j2 <= 0 || k2 != k && k2 != l - 1) {
+                            blockposition_mutableblockposition.set(l1, k2, i2);
+                            BlockState iblockdata = this.level().getBlockState(blockposition_mutableblockposition);
+
+                            if (!(iblockdata.getBlock() instanceof WaterlilyBlock) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level(), blockposition_mutableblockposition).move((double) l1, (double) k2, (double) i2), voxelshape, BooleanOp.AND)) {
+                                f += iblockdata.getBlock().getFriction();
+                                ++k1;
                             }
                         }
                     }
@@ -528,94 +606,99 @@
             }
         }
 
-        return f / (float)o;
+        return f / (float) k1;
     }
 
     private boolean checkInWater() {
-        AABB aABB = this.getBoundingBox();
-        int i = Mth.floor(aABB.minX);
-        int j = Mth.ceil(aABB.maxX);
-        int k = Mth.floor(aABB.minY);
-        int l = Mth.ceil(aABB.minY + 0.001);
-        int m = Mth.floor(aABB.minZ);
-        int n = Mth.ceil(aABB.maxZ);
-        boolean bl = false;
-        this.waterLevel = -Double.MAX_VALUE;
-        BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+        AABB axisalignedbb = this.getBoundingBox();
+        int i = Mth.floor(axisalignedbb.minX);
+        int j = Mth.ceil(axisalignedbb.maxX);
+        int k = Mth.floor(axisalignedbb.minY);
+        int l = Mth.ceil(axisalignedbb.minY + 0.001D);
+        int i1 = Mth.floor(axisalignedbb.minZ);
+        int j1 = Mth.ceil(axisalignedbb.maxZ);
+        boolean flag = false;
 
-        for (int o = i; o < j; o++) {
-            for (int p = k; p < l; p++) {
-                for (int q = m; q < n; q++) {
-                    mutableBlockPos.set(o, p, q);
-                    FluidState fluidState = this.level().getFluidState(mutableBlockPos);
-                    if (fluidState.is(FluidTags.WATER)) {
-                        float f = (float)p + fluidState.getHeight(this.level(), mutableBlockPos);
-                        this.waterLevel = Math.max((double)f, this.waterLevel);
-                        bl |= aABB.minY < (double)f;
+        this.waterLevel = -1.7976931348623157E308D;
+        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+
+        for (int k1 = i; k1 < j; ++k1) {
+            for (int l1 = k; l1 < l; ++l1) {
+                for (int i2 = i1; i2 < j1; ++i2) {
+                    blockposition_mutableblockposition.set(k1, l1, i2);
+                    FluidState fluid = this.level().getFluidState(blockposition_mutableblockposition);
+
+                    if (fluid.is(FluidTags.WATER)) {
+                        float f = (float) l1 + fluid.getHeight(this.level(), blockposition_mutableblockposition);
+
+                        this.waterLevel = Math.max((double) f, this.waterLevel);
+                        flag |= axisalignedbb.minY < (double) f;
                     }
                 }
             }
         }
 
-        return bl;
+        return flag;
     }
 
     @Nullable
     private Boat.Status isUnderwater() {
-        AABB aABB = this.getBoundingBox();
-        double d = aABB.maxY + 0.001;
-        int i = Mth.floor(aABB.minX);
-        int j = Mth.ceil(aABB.maxX);
-        int k = Mth.floor(aABB.maxY);
-        int l = Mth.ceil(d);
-        int m = Mth.floor(aABB.minZ);
-        int n = Mth.ceil(aABB.maxZ);
-        boolean bl = false;
-        BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+        AABB axisalignedbb = this.getBoundingBox();
+        double d0 = axisalignedbb.maxY + 0.001D;
+        int i = Mth.floor(axisalignedbb.minX);
+        int j = Mth.ceil(axisalignedbb.maxX);
+        int k = Mth.floor(axisalignedbb.maxY);
+        int l = Mth.ceil(d0);
+        int i1 = Mth.floor(axisalignedbb.minZ);
+        int j1 = Mth.ceil(axisalignedbb.maxZ);
+        boolean flag = false;
+        BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
 
-        for (int o = i; o < j; o++) {
-            for (int p = k; p < l; p++) {
-                for (int q = m; q < n; q++) {
-                    mutableBlockPos.set(o, p, q);
-                    FluidState fluidState = this.level().getFluidState(mutableBlockPos);
-                    if (fluidState.is(FluidTags.WATER) && d < (double)((float)mutableBlockPos.getY() + fluidState.getHeight(this.level(), mutableBlockPos))) {
-                        if (!fluidState.isSource()) {
+        for (int k1 = i; k1 < j; ++k1) {
+            for (int l1 = k; l1 < l; ++l1) {
+                for (int i2 = i1; i2 < j1; ++i2) {
+                    blockposition_mutableblockposition.set(k1, l1, i2);
+                    FluidState fluid = this.level().getFluidState(blockposition_mutableblockposition);
+
+                    if (fluid.is(FluidTags.WATER) && d0 < (double) ((float) blockposition_mutableblockposition.getY() + fluid.getHeight(this.level(), blockposition_mutableblockposition))) {
+                        if (!fluid.isSource()) {
                             return Boat.Status.UNDER_FLOWING_WATER;
                         }
 
-                        bl = true;
+                        flag = true;
                     }
                 }
             }
         }
 
-        return bl ? Boat.Status.UNDER_WATER : null;
+        return flag ? Boat.Status.UNDER_WATER : null;
     }
 
     @Override
     protected double getDefaultGravity() {
-        return 0.04;
+        return 0.04D;
     }
 
     private void floatBoat() {
-        double d = -this.getGravity();
-        double e = 0.0;
+        double d0 = -this.getGravity();
+        double d1 = 0.0D;
+
         this.invFriction = 0.05F;
         if (this.oldStatus == Boat.Status.IN_AIR && this.status != Boat.Status.IN_AIR && this.status != Boat.Status.ON_LAND) {
-            this.waterLevel = this.getY(1.0);
-            this.setPos(this.getX(), (double)(this.getWaterLevelAbove() - this.getBbHeight()) + 0.101, this.getZ());
-            this.setDeltaMovement(this.getDeltaMovement().multiply(1.0, 0.0, 1.0));
-            this.lastYd = 0.0;
+            this.waterLevel = this.getY(1.0D);
+            this.move(MoverType.SELF, new Vec3(0.0, ((double) (this.getWaterLevelAbove() - this.getBbHeight()) + 0.101D) - this.getY(), 0.0)); // Paper - Improve boat collision performance
+            this.setDeltaMovement(this.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D));
+            this.lastYd = 0.0D;
             this.status = Boat.Status.IN_WATER;
         } else {
             if (this.status == Boat.Status.IN_WATER) {
-                e = (this.waterLevel - this.getY()) / (double)this.getBbHeight();
+                d1 = (this.waterLevel - this.getY()) / (double) this.getBbHeight();
                 this.invFriction = 0.9F;
             } else if (this.status == Boat.Status.UNDER_FLOWING_WATER) {
-                d = -7.0E-4;
+                d0 = -7.0E-4D;
                 this.invFriction = 0.9F;
             } else if (this.status == Boat.Status.UNDER_WATER) {
-                e = 0.01F;
+                d1 = 0.009999999776482582D;
                 this.invFriction = 0.45F;
             } else if (this.status == Boat.Status.IN_AIR) {
                 this.invFriction = 0.9F;
@@ -626,25 +709,29 @@
                 }
             }
 
-            Vec3 vec3 = this.getDeltaMovement();
-            this.setDeltaMovement(vec3.x * (double)this.invFriction, vec3.y + d, vec3.z * (double)this.invFriction);
-            this.deltaRotation = this.deltaRotation * this.invFriction;
-            if (e > 0.0) {
-                Vec3 vec32 = this.getDeltaMovement();
-                this.setDeltaMovement(vec32.x, (vec32.y + e * (this.getDefaultGravity() / 0.65)) * 0.75, vec32.z);
+            Vec3 vec3d = this.getDeltaMovement();
+
+            this.setDeltaMovement(vec3d.x * (double) this.invFriction, vec3d.y + d0, vec3d.z * (double) this.invFriction);
+            this.deltaRotation *= this.invFriction;
+            if (d1 > 0.0D) {
+                Vec3 vec3d1 = this.getDeltaMovement();
+
+                this.setDeltaMovement(vec3d1.x, (vec3d1.y + d1 * (this.getDefaultGravity() / 0.65D)) * 0.75D, vec3d1.z);
             }
         }
+
     }
 
     private void controlBoat() {
         if (this.isVehicle()) {
             float f = 0.0F;
+
             if (this.inputLeft) {
-                this.deltaRotation--;
+                --this.deltaRotation;
             }
 
             if (this.inputRight) {
-                this.deltaRotation++;
+                ++this.deltaRotation;
             }
 
             if (this.inputRight != this.inputLeft && !this.inputUp && !this.inputDown) {
@@ -660,14 +747,7 @@
                 f -= 0.005F;
             }
 
-            this.setDeltaMovement(
-                this.getDeltaMovement()
-                    .add(
-                        (double)(Mth.sin(-this.getYRot() * (float) (Math.PI / 180.0)) * f),
-                        0.0,
-                        (double)(Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)) * f)
-                    )
-            );
+            this.setDeltaMovement(this.getDeltaMovement().add((double) (Mth.sin(-this.getYRot() * 0.017453292F) * f), 0.0D, (double) (Mth.cos(this.getYRot() * 0.017453292F) * f)));
             this.setPaddleState(this.inputRight && !this.inputLeft || this.inputUp, this.inputLeft && !this.inputRight || this.inputUp);
         }
     }
@@ -689,36 +769,48 @@
             this.clampRotation(passenger);
             if (passenger instanceof Animal && this.getPassengers().size() == this.getMaxPassengers()) {
                 int i = passenger.getId() % 2 == 0 ? 90 : 270;
-                passenger.setYBodyRot(((Animal)passenger).yBodyRot + (float)i);
-                passenger.setYHeadRot(passenger.getYHeadRot() + (float)i);
+
+                passenger.setYBodyRot(((Animal) passenger).yBodyRot + (float) i);
+                passenger.setYHeadRot(passenger.getYHeadRot() + (float) i);
             }
+
         }
     }
 
     @Override
     public Vec3 getDismountLocationForPassenger(LivingEntity passenger) {
-        Vec3 vec3 = getCollisionHorizontalEscapeVector((double)(this.getBbWidth() * Mth.SQRT_OF_TWO), (double)passenger.getBbWidth(), passenger.getYRot());
-        double d = this.getX() + vec3.x;
-        double e = this.getZ() + vec3.z;
-        BlockPos blockPos = BlockPos.containing(d, this.getBoundingBox().maxY, e);
-        BlockPos blockPos2 = blockPos.below();
-        if (!this.level().isWaterAt(blockPos2)) {
+        Vec3 vec3d = getCollisionHorizontalEscapeVector((double) (this.getBbWidth() * Mth.SQRT_OF_TWO), (double) passenger.getBbWidth(), passenger.getYRot());
+        double d0 = this.getX() + vec3d.x;
+        double d1 = this.getZ() + vec3d.z;
+        BlockPos blockposition = BlockPos.containing(d0, this.getBoundingBox().maxY, d1);
+        BlockPos blockposition1 = blockposition.below();
+
+        if (!this.level().isWaterAt(blockposition1)) {
             List<Vec3> list = Lists.newArrayList();
-            double f = this.level().getBlockFloorHeight(blockPos);
-            if (DismountHelper.isBlockFloorValid(f)) {
-                list.add(new Vec3(d, (double)blockPos.getY() + f, e));
+            double d2 = this.level().getBlockFloorHeight(blockposition);
+
+            if (DismountHelper.isBlockFloorValid(d2)) {
+                list.add(new Vec3(d0, (double) blockposition.getY() + d2, d1));
             }
 
-            double g = this.level().getBlockFloorHeight(blockPos2);
-            if (DismountHelper.isBlockFloorValid(g)) {
-                list.add(new Vec3(d, (double)blockPos2.getY() + g, e));
+            double d3 = this.level().getBlockFloorHeight(blockposition1);
+
+            if (DismountHelper.isBlockFloorValid(d3)) {
+                list.add(new Vec3(d0, (double) blockposition1.getY() + d3, d1));
             }
 
-            for (Pose pose : passenger.getDismountPoses()) {
-                for (Vec3 vec32 : list) {
-                    if (DismountHelper.canDismountTo(this.level(), vec32, passenger, pose)) {
-                        passenger.setPose(pose);
-                        return vec32;
+            UnmodifiableIterator unmodifiableiterator = passenger.getDismountPoses().iterator();
+
+            while (unmodifiableiterator.hasNext()) {
+                Pose entitypose = (Pose) unmodifiableiterator.next();
+                Iterator iterator = list.iterator();
+
+                while (iterator.hasNext()) {
+                    Vec3 vec3d1 = (Vec3) iterator.next();
+
+                    if (DismountHelper.canDismountTo(this.level(), vec3d1, passenger, entitypose)) {
+                        passenger.setPose(entitypose);
+                        return vec3d1;
                     }
                 }
             }
@@ -730,9 +822,10 @@
     protected void clampRotation(Entity passenger) {
         passenger.setYBodyRot(this.getYRot());
         float f = Mth.wrapDegrees(passenger.getYRot() - this.getYRot());
-        float g = Mth.clamp(f, -105.0F, 105.0F);
-        passenger.yRotO += g - f;
-        passenger.setYRot(passenger.getYRot() + g - f);
+        float f1 = Mth.clamp(f, -105.0F, 105.0F);
+
+        passenger.yRotO += f1 - f;
+        passenger.setYRot(passenger.getYRot() + f1 - f);
         passenger.setYHeadRot(passenger.getYRot());
     }
 
@@ -751,21 +844,12 @@
         if (nbt.contains("Type", 8)) {
             this.setVariant(Boat.Type.byName(nbt.getString("Type")));
         }
+
     }
 
     @Override
     public InteractionResult interact(Player player, InteractionHand hand) {
-        if (player.isSecondaryUseActive()) {
-            return InteractionResult.PASS;
-        } else if (this.outOfControlTicks < 60.0F) {
-            if (!this.level().isClientSide) {
-                return player.startRiding(this) ? InteractionResult.CONSUME : InteractionResult.PASS;
-            } else {
-                return InteractionResult.SUCCESS;
-            }
-        } else {
-            return InteractionResult.PASS;
-        }
+        return player.isSecondaryUseActive() ? InteractionResult.PASS : (this.outOfControlTicks < 60.0F ? (!this.level().isClientSide ? (player.startRiding(this) ? InteractionResult.CONSUME : InteractionResult.PASS) : InteractionResult.SUCCESS) : InteractionResult.PASS);
     }
 
     @Override
@@ -781,50 +865,58 @@
 
                     this.causeFallDamage(this.fallDistance, 1.0F, this.damageSources().fall());
                     if (!this.level().isClientSide && !this.isRemoved()) {
+                    // CraftBukkit start
+                    Vehicle vehicle = (Vehicle) this.getBukkitEntity();
+                    VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, null);
+                    this.level().getCraftServer().getPluginManager().callEvent(destroyEvent);
+                    if (!destroyEvent.isCancelled()) {
                         this.kill();
                         if (this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
-                            for (int i = 0; i < 3; i++) {
-                                this.spawnAtLocation(this.getVariant().getPlanks());
+                            int i;
+
+                            for (i = 0; i < 3; ++i) {
+                                this.spawnAtLocation((ItemLike) this.getVariant().getPlanks());
                             }
 
-                            for (int j = 0; j < 2; j++) {
-                                this.spawnAtLocation(Items.STICK);
+                            for (i = 0; i < 2; ++i) {
+                                this.spawnAtLocation((ItemLike) Items.STICK);
                             }
                         }
                     }
+                    } // CraftBukkit end
                 }
 
                 this.resetFallDistance();
-            } else if (!this.level().getFluidState(this.blockPosition().below()).is(FluidTags.WATER) && heightDifference < 0.0) {
-                this.fallDistance -= (float)heightDifference;
+            } else if (!this.level().getFluidState(this.blockPosition().below()).is(FluidTags.WATER) && heightDifference < 0.0D) {
+                this.fallDistance -= (float) heightDifference;
             }
+
         }
     }
 
     public boolean getPaddleState(int paddle) {
-        return this.entityData.get(paddle == 0 ? DATA_ID_PADDLE_LEFT : DATA_ID_PADDLE_RIGHT) && this.getControllingPassenger() != null;
+        return (Boolean) this.entityData.get(paddle == 0 ? Boat.DATA_ID_PADDLE_LEFT : Boat.DATA_ID_PADDLE_RIGHT) && this.getControllingPassenger() != null;
     }
 
     private void setBubbleTime(int wobbleTicks) {
-        this.entityData.set(DATA_ID_BUBBLE_TIME, wobbleTicks);
+        this.entityData.set(Boat.DATA_ID_BUBBLE_TIME, wobbleTicks);
     }
 
     private int getBubbleTime() {
-        return this.entityData.get(DATA_ID_BUBBLE_TIME);
+        return (Integer) this.entityData.get(Boat.DATA_ID_BUBBLE_TIME);
     }
 
     public float getBubbleAngle(float tickDelta) {
         return Mth.lerp(tickDelta, this.bubbleAngleO, this.bubbleAngle);
     }
 
-    @Override
     public void setVariant(Boat.Type variant) {
-        this.entityData.set(DATA_ID_TYPE, variant.ordinal());
+        this.entityData.set(Boat.DATA_ID_TYPE, variant.ordinal());
     }
 
     @Override
     public Boat.Type getVariant() {
-        return Boat.Type.byId(this.entityData.get(DATA_ID_TYPE));
+        return Boat.Type.byId((Integer) this.entityData.get(Boat.DATA_ID_TYPE));
     }
 
     @Override
@@ -839,7 +931,16 @@
     @Nullable
     @Override
     public LivingEntity getControllingPassenger() {
-        return this.getFirstPassenger() instanceof LivingEntity livingEntity ? livingEntity : super.getControllingPassenger();
+        Entity entity = this.getFirstPassenger();
+        LivingEntity entityliving;
+
+        if (entity instanceof LivingEntity entityliving1) {
+            entityliving = entityliving1;
+        } else {
+            entityliving = super.getControllingPassenger();
+        }
+
+        return entityliving;
     }
 
     public void setInput(boolean pressingLeft, boolean pressingRight, boolean pressingForward, boolean pressingBack) {
@@ -864,33 +965,18 @@
         return new ItemStack(this.getDropItem());
     }
 
-    public static enum Status {
-        IN_WATER,
-        UNDER_WATER,
-        UNDER_FLOWING_WATER,
-        ON_LAND,
-        IN_AIR;
-    }
-
     public static enum Type implements StringRepresentable {
-        OAK(Blocks.OAK_PLANKS, "oak"),
-        SPRUCE(Blocks.SPRUCE_PLANKS, "spruce"),
-        BIRCH(Blocks.BIRCH_PLANKS, "birch"),
-        JUNGLE(Blocks.JUNGLE_PLANKS, "jungle"),
-        ACACIA(Blocks.ACACIA_PLANKS, "acacia"),
-        CHERRY(Blocks.CHERRY_PLANKS, "cherry"),
-        DARK_OAK(Blocks.DARK_OAK_PLANKS, "dark_oak"),
-        MANGROVE(Blocks.MANGROVE_PLANKS, "mangrove"),
-        BAMBOO(Blocks.BAMBOO_PLANKS, "bamboo");
+
+        OAK(Blocks.OAK_PLANKS, "oak"), SPRUCE(Blocks.SPRUCE_PLANKS, "spruce"), BIRCH(Blocks.BIRCH_PLANKS, "birch"), JUNGLE(Blocks.JUNGLE_PLANKS, "jungle"), ACACIA(Blocks.ACACIA_PLANKS, "acacia"), CHERRY(Blocks.CHERRY_PLANKS, "cherry"), DARK_OAK(Blocks.DARK_OAK_PLANKS, "dark_oak"), MANGROVE(Blocks.MANGROVE_PLANKS, "mangrove"), BAMBOO(Blocks.BAMBOO_PLANKS, "bamboo");
 
         private final String name;
         private final Block planks;
         public static final StringRepresentable.EnumCodec<Boat.Type> CODEC = StringRepresentable.fromEnum(Boat.Type::values);
         private static final IntFunction<Boat.Type> BY_ID = ByIdMap.continuous(Enum::ordinal, values(), ByIdMap.OutOfBoundsStrategy.ZERO);
 
-        private Type(final Block baseBlock, final String name) {
-            this.name = name;
-            this.planks = baseBlock;
+        private Type(final Block block, final String s) {
+            this.name = s;
+            this.planks = block;
         }
 
         @Override
@@ -906,17 +992,23 @@
             return this.planks;
         }
 
-        @Override
         public String toString() {
             return this.name;
         }
 
         public static Boat.Type byId(int type) {
-            return BY_ID.apply(type);
+            return (Boat.Type) Boat.Type.BY_ID.apply(type);
         }
 
         public static Boat.Type byName(String name) {
-            return CODEC.byName(name, OAK);
+            return (Boat.Type) Boat.Type.CODEC.byName(name, Boat.Type.OAK);
         }
     }
+
+    public static enum Status {
+
+        IN_WATER, UNDER_WATER, UNDER_FLOWING_WATER, ON_LAND, IN_AIR;
+
+        private Status() {}
+    }
 }
