--- a/net/minecraft/world/entity/animal/horse/AbstractHorse.java
+++ b/net/minecraft/world/entity/animal/horse/AbstractHorse.java
@@ -77,6 +_,17 @@
 import net.minecraft.world.phys.Vec3;
 import net.minecraft.world.ticks.ContainerSingleItem;
 
+// CraftBukkit start
+import java.util.Arrays;
+import java.util.List;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.event.entity.EntityRegainHealthEvent;
+import org.bukkit.inventory.InventoryHolder;
+// CraftBukkit end
+
 public abstract class AbstractHorse extends Animal implements ContainerListener, HasCustomInventoryScreen, OwnableEntity, PlayerRideableJumping, Saddleable {
     public static final int EQUIPMENT_SLOT_OFFSET = 400;
     public static final int CHEST_SLOT_OFFSET = 499;
@@ -112,7 +_,7 @@
     public int tailCounter;
     public int sprintCounter;
     protected boolean isJumping;
-    protected SimpleContainer inventory;
+    public SimpleContainer inventory;
     protected int temper;
     protected float playerJumpPendingScale;
     protected boolean allowStandSliding;
@@ -145,7 +_,53 @@
         public boolean stillValid(Player player) {
             return player.getVehicle() == AbstractHorse.this || player.canInteractWithEntity(AbstractHorse.this, 4.0);
         }
+
+        // CraftBukkit start - add fields and methods
+        public final List<HumanEntity> transaction = new java.util.ArrayList<>();
+        private int maxStack = MAX_STACK;
+
+        @Override
+        public List<ItemStack> getContents() {
+            return Arrays.asList(this.getTheItem());
+        }
+
+        @Override
+        public void onOpen(CraftHumanEntity player) {
+            this.transaction.add(player);
+        }
+
+        @Override
+        public void onClose(CraftHumanEntity player) {
+            this.transaction.remove(player);
+        }
+
+        @Override
+        public List<HumanEntity> getViewers() {
+            return this.transaction;
+        }
+
+        @Override
+        public int getMaxStackSize() {
+            return this.maxStack;
+        }
+
+        @Override
+        public void setMaxStackSize(int size) {
+            this.maxStack = size;
+        }
+
+        @Override
+        public InventoryHolder getOwner() {
+            return (org.bukkit.entity.AbstractHorse) AbstractHorse.this.getBukkitEntity();
+        }
+
+        @Override
+        public Location getLocation() {
+            return AbstractHorse.this.getBukkitEntity().getLocation();
+        }
+        // CraftBukkit end
     };
+    public int maxDomestication = 100; // CraftBukkit - store max domestication value
 
     protected AbstractHorse(EntityType<? extends AbstractHorse> entityType, Level level) {
         super(entityType, level);
@@ -284,7 +_,7 @@
     }
 
     @Override
-    public boolean isPushable() {
+    public boolean isCollidable(boolean ignoreClimbing) { // Paper - Climbing should not bypass cramming gamerule
         return !this.isVehicle();
     }
 
@@ -338,9 +_,9 @@
         return columns * 3 + 1;
     }
 
-    protected void createInventory() {
+    public void createInventory() {
         SimpleContainer simpleContainer = this.inventory;
-        this.inventory = new SimpleContainer(this.getInventorySize());
+        this.inventory = new SimpleContainer(this.getInventorySize(), (org.bukkit.entity.AbstractHorse) this.getBukkitEntity()); // CraftBukkit
         if (simpleContainer != null) {
             simpleContainer.removeListener(this);
             int min = Math.min(simpleContainer.getContainerSize(), this.inventory.getContainerSize());
@@ -448,11 +_,11 @@
     }
 
     public int getMaxTemper() {
-        return 100;
+        return this.maxDomestication; // CraftBukkit - return stored max domestication instead of 100
     }
 
     @Override
-    protected float getSoundVolume() {
+    public float getSoundVolume() {
         return 0.8F;
     }
 
@@ -503,7 +_,7 @@
             i1 = 5;
             if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) {
                 flag = true;
-                this.setInLove(player);
+                this.setInLove(player, stack.copy()); // Paper - Fix EntityBreedEvent copying
             }
         } else if (stack.is(Items.GOLDEN_APPLE) || stack.is(Items.ENCHANTED_GOLDEN_APPLE)) {
             f = 10.0F;
@@ -511,12 +_,12 @@
             i1 = 10;
             if (!this.level().isClientSide && this.isTamed() && this.getAge() == 0 && !this.isInLove()) {
                 flag = true;
-                this.setInLove(player);
+                this.setInLove(player, stack.copy()); // Paper - Fix EntityBreedEvent copying
             }
         }
 
         if (this.getHealth() < this.getMaxHealth() && f > 0.0F) {
-            this.heal(f);
+            this.heal(f, EntityRegainHealthEvent.RegainReason.EATING); // CraftBukkit
             flag = true;
         }
 
@@ -587,7 +_,7 @@
         super.aiStep();
         if (this.level() instanceof ServerLevel serverLevel && this.isAlive()) {
             if (this.random.nextInt(900) == 0 && this.deathTime == 0) {
-                this.heal(1.0F);
+                this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
             }
 
             if (this.canEatGrass()) {
@@ -690,6 +_,16 @@
         }
     }
 
+    // Paper start - Horse API
+    public void setMouthOpen(boolean open) {
+        this.setFlag(FLAG_OPEN_MOUTH, open);
+    }
+
+    public boolean isMouthOpen() {
+        return this.getFlag(FLAG_OPEN_MOUTH);
+    }
+    // Paper end - Horse API
+
     @Override
     public InteractionResult mobInteract(Player player, InteractionHand hand) {
         if (this.isVehicle() || this.isBaby()) {
@@ -727,6 +_,12 @@
         this.setFlag(16, eating);
     }
 
+    // Paper start - Horse API
+    public void setForceStanding(boolean standing) {
+        this.setFlag(FLAG_STANDING, standing);
+    }
+    // Paper end - Horse API
+
     public void setStanding(boolean standing) {
         if (standing) {
             this.setEating(false);
@@ -838,6 +_,7 @@
         if (this.getOwnerUUID() != null) {
             compound.putUUID("Owner", this.getOwnerUUID());
         }
+        compound.putInt("Bukkit.MaxDomestication", this.maxDomestication); // CraftBukkit
 
         if (!this.inventory.getItem(0).isEmpty()) {
             compound.put("SaddleItem", this.inventory.getItem(0).save(this.registryAccess()));
@@ -862,6 +_,11 @@
         if (uuid != null) {
             this.setOwnerUUID(uuid);
         }
+        // CraftBukkit start
+        if (compound.contains("Bukkit.MaxDomestication")) {
+            this.maxDomestication = compound.getInt("Bukkit.MaxDomestication");
+        }
+        // CraftBukkit end
 
         if (compound.contains("SaddleItem", 10)) {
             ItemStack itemStack = ItemStack.parse(this.registryAccess(), compound.getCompound("SaddleItem")).orElse(ItemStack.EMPTY);
@@ -959,6 +_,17 @@
 
     @Override
     public void handleStartJump(int jumpPower) {
+        // CraftBukkit start
+        float power;
+        if (jumpPower >= 90) {
+            power = 1.0F;
+        } else {
+            power = 0.4F + 0.4F * (float) jumpPower / 90.0F;
+        }
+        if (!CraftEventFactory.callHorseJumpEvent(this, power)) {
+            return;
+        }
+        // CraftBukkit end
         this.allowStandSliding = true;
         this.standIfPossible();
         this.playJumpSound();
