--- a/net/minecraft/world/entity/vehicle/AbstractBoat.java
+++ b/net/minecraft/world/entity/vehicle/AbstractBoat.java
@@ -66,7 +_,7 @@
     private boolean inputDown;
     private double waterLevel;
     private float landFriction;
-    private AbstractBoat.Status status;
+    public AbstractBoat.Status status;
     private AbstractBoat.Status oldStatus;
     private double lastYd;
     private boolean isAboveBubbleColumn;
@@ -78,6 +_,15 @@
     private Leashable.LeashData leashData;
     private final Supplier<Item> dropItem;
 
+    // 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;
+    private org.bukkit.Location lastLocation;
+    // CraftBukkit end
+
     public AbstractBoat(EntityType<? extends AbstractBoat> entityType, Level level, Supplier<Item> dropItem) {
         super(entityType, level);
         this.dropItem = dropItem;
@@ -119,7 +_,7 @@
     }
 
     @Override
-    public boolean isPushable() {
+    public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule
         return true;
     }
 
@@ -174,11 +_,30 @@
 
     @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 AbstractBoat) {
             if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) {
+                // CraftBukkit start
+                if (!this.isPassengerOfSameVehicle(entity)) {
+                    org.bukkit.event.vehicle.VehicleEntityCollisionEvent event = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent(
+                        (org.bukkit.entity.Vehicle) this.getBukkitEntity(),
+                        entity.getBukkitEntity()
+                    );
+                    if (!event.callEvent()) return;
+                }
+                // CraftBukkit end
                 super.push(entity);
             }
         } else if (entity.getBoundingBox().minY <= this.getBoundingBox().minY) {
+            // CraftBukkit start
+            if (!this.isPassengerOfSameVehicle(entity)) {
+                org.bukkit.event.vehicle.VehicleEntityCollisionEvent event = new org.bukkit.event.vehicle.VehicleEntityCollisionEvent(
+                    (org.bukkit.entity.Vehicle) this.getBukkitEntity(),
+                    entity.getBukkitEntity()
+                );
+                if (!event.callEvent()) return;
+            }
+            // CraftBukkit end
             super.push(entity);
         }
     }
@@ -245,6 +_,18 @@
             this.setDeltaMovement(Vec3.ZERO);
         }
 
+        // CraftBukkit start
+        org.bukkit.Location to = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.position(), this.level(), this.getYRot(), this.getXRot());
+        org.bukkit.entity.Vehicle vehicle = (org.bukkit.entity.Vehicle) this.getBukkitEntity();
+
+        new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle).callEvent();
+
+        if (this.lastLocation != null && !this.lastLocation.equals(to)) {
+            org.bukkit.event.vehicle.VehicleMoveEvent event = new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, this.lastLocation, to);
+            event.callEvent();
+        }
+        this.lastLocation = vehicle.getLocation();
+        // CraftBukkit end
         this.applyEffectsFromBlocks();
         this.applyEffectsFromBlocks();
         this.tickBubbleColumn();
@@ -381,7 +_,7 @@
         return Leashable.createQuadLeashOffsets(this, 0.0, 0.64, 0.382, 0.88);
     }
 
-    private AbstractBoat.Status getStatus() {
+    public AbstractBoat.Status getStatus() {
         AbstractBoat.Status status = this.isUnderwater();
         if (status != null) {
             this.waterLevel = this.getBoundingBox().maxY;
@@ -547,7 +_,7 @@
             this.waterLevel = this.getY(1.0);
             double d2 = this.getWaterLevelAbove() - this.getBbHeight() + 0.101;
             if (this.level().noCollision(this, this.getBoundingBox().move(0.0, d2 - this.getY(), 0.0))) {
-                this.setPos(this.getX(), d2, this.getZ());
+                this.move(MoverType.SELF, new Vec3(0.0D, d2 - this.getY(), 0.0D)); // Paper - Fix some exploit with boats // TODO Still needed?
                 this.setDeltaMovement(this.getDeltaMovement().multiply(1.0, 0.0, 1.0));
                 this.lastYd = 0.0;
             }
@@ -710,12 +_,12 @@
     }
 
     @Override
-    public void remove(Entity.RemovalReason reason) {
+    public void remove(Entity.RemovalReason reason, @Nullable org.bukkit.event.entity.EntityRemoveEvent.Cause eventCause) { // CraftBukkit - add Bukkit remove cause
         if (!this.level().isClientSide() && reason.shouldDestroy() && this.isLeashed()) {
             this.dropLeash();
         }
 
-        super.remove(reason);
+        super.remove(reason, eventCause); // CraftBukkit - add Bukkit remove cause
     }
 
     @Override
@@ -774,7 +_,7 @@
     }
 
     @Override
-    protected final Item getDropItem() {
+    public final Item getDropItem() {
         return this.dropItem.get();
     }
 
