From 2addb938a80b0bd6ae35d19695de74684d00ddaf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 13 Oct 2026 15:51:09 +0000 Subject: [PATCH 1/5] fix: apply VehicleDamageEvent damage on boats (TODO #3) EntityBoat honored cancel but ignored event.getDamage(), so plugins could not change boat damage. Apply the event damage the same way EntityMinecartAbstract already does. Co-authored-by: xii69 --- .../java/net/minecraft/server/EntityBoat.java | 3 +- .../VehicleDamageEventBoatParityTest.java | 114 ++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 WindSpigot-Server/src/test/java/com/windpvp/windspigot/vehicle/VehicleDamageEventBoatParityTest.java diff --git a/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java b/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java index 7452a9661..637dc6a76 100644 --- a/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java +++ b/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java @@ -120,7 +120,8 @@ public boolean damageEntity(DamageSource damagesource, float f) { if (event.isCancelled()) { return false; // FalchusSpigot - fix SPIGOT-4647: Properly cancel VehicleDamageEvent } - // f = event.getDamage(); // TODO Why don't we do this? + // Match EntityMinecartAbstract: honor plugin-modified VehicleDamageEvent damage + f = (float) event.getDamage(); // CraftBukkit end this.b(-this.m()); diff --git a/WindSpigot-Server/src/test/java/com/windpvp/windspigot/vehicle/VehicleDamageEventBoatParityTest.java b/WindSpigot-Server/src/test/java/com/windpvp/windspigot/vehicle/VehicleDamageEventBoatParityTest.java new file mode 100644 index 000000000..4342e68ec --- /dev/null +++ b/WindSpigot-Server/src/test/java/com/windpvp/windspigot/vehicle/VehicleDamageEventBoatParityTest.java @@ -0,0 +1,114 @@ +package com.windpvp.windspigot.vehicle; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.vehicle.VehicleDamageEvent; +import org.junit.Assert; +import org.junit.Test; + +/** + * Regression for EntityBoat VehicleDamageEvent damage application (TODO #3). + *

+ * Boats previously fired and honored cancel, but ignored {@code event.getDamage()}. + * Minecarts already applied modified damage — these tests lock that shared contract. + *

+ * Uses JDK proxies instead of Mockito (1.10.19 is incompatible with modern JDKs). + */ +public class VehicleDamageEventBoatParityTest { + + /** Same accumulation used by EntityBoat and EntityMinecartAbstract after the event. */ + private static float applyVehicleHitDamage(float currentDamage, float eventDamage) { + return currentDamage + eventDamage * 10.0F; + } + + @SuppressWarnings("unchecked") + private static T stub(Class type) { + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + Class r = method.getReturnType(); + if (r == boolean.class) { + return false; + } + if (r == int.class || r == byte.class || r == short.class || r == char.class) { + return 0; + } + if (r == long.class) { + return 0L; + } + if (r == float.class) { + return 0.0F; + } + if (r == double.class) { + return 0.0D; + } + if (r == void.class) { + return null; + } + if (method.getName().equals("equals")) { + return proxy == args[0]; + } + if (method.getName().equals("hashCode")) { + return System.identityHashCode(proxy); + } + if (method.getName().equals("toString")) { + return "stub:" + type.getSimpleName(); + } + return null; + } + }; + return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, handler); + } + + @Test + public void pluginModifiedDamageIsAppliedLikeMinecart() { + Vehicle vehicle = stub(Vehicle.class); + Entity attacker = stub(Entity.class); + + float vanillaHit = 4.0F; + VehicleDamageEvent event = new VehicleDamageEvent(vehicle, attacker, vanillaHit); + event.setDamage(1.5D); + + Assert.assertFalse(event.isCancelled()); + + float f = (float) event.getDamage(); + float boatDamage = applyVehicleHitDamage(0.0F, f); + + Assert.assertEquals(1.5F, f, 0.0001F); + Assert.assertEquals(15.0F, boatDamage, 0.0001F); + // Pre-fix behavior ignored setDamage and would have applied vanillaHit instead: + Assert.assertNotEquals(applyVehicleHitDamage(0.0F, vanillaHit), boatDamage, 0.0001F); + } + + @Test + public void cancelledEventMeansNoDamageApplied() { + Vehicle vehicle = stub(Vehicle.class); + Entity attacker = stub(Entity.class); + + VehicleDamageEvent event = new VehicleDamageEvent(vehicle, attacker, 4.0D); + event.setCancelled(true); + event.setDamage(99.0D); // should be irrelevant when cancelled + + Assert.assertTrue(event.isCancelled()); + // EntityBoat returns false before applying damage when cancelled (SPIGOT-4647). + if (!event.isCancelled()) { + Assert.fail("cancel path must short-circuit before setDamage"); + } + } + + @Test + public void unmodifiedDamageMatchesVanillaHit() { + Vehicle vehicle = stub(Vehicle.class); + + float vanillaHit = 2.0F; + VehicleDamageEvent event = new VehicleDamageEvent(vehicle, null, vanillaHit); + + float f = (float) event.getDamage(); + Assert.assertEquals(vanillaHit, f, 0.0001F); + Assert.assertEquals(20.0F, applyVehicleHitDamage(0.0F, f), 0.0001F); + } +} From 66e7776d8da3e6b37e6093d99197c2d43ff0a04b Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 14 Oct 2026 20:27:20 +0200 Subject: [PATCH 2/5] Update EntityBoat.java --- .../src/main/java/net/minecraft/server/EntityBoat.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java b/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java index 637dc6a76..b69bef925 100644 --- a/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java +++ b/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java @@ -120,8 +120,7 @@ public boolean damageEntity(DamageSource damagesource, float f) { if (event.isCancelled()) { return false; // FalchusSpigot - fix SPIGOT-4647: Properly cancel VehicleDamageEvent } - // Match EntityMinecartAbstract: honor plugin-modified VehicleDamageEvent damage - f = (float) event.getDamage(); + f = (float) event.getDamage(); // PandaSpigot // CraftBukkit end this.b(-this.m()); From 143c997338c58e87c4e0833a48ef4f29d44a65bb Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 14 Oct 2026 20:27:31 +0200 Subject: [PATCH 3/5] Delete WindSpigot-Server/src/test/java/com/windpvp/windspigot/vehicle/VehicleDamageEventBoatParityTest.java --- .../VehicleDamageEventBoatParityTest.java | 114 ------------------ 1 file changed, 114 deletions(-) delete mode 100644 WindSpigot-Server/src/test/java/com/windpvp/windspigot/vehicle/VehicleDamageEventBoatParityTest.java diff --git a/WindSpigot-Server/src/test/java/com/windpvp/windspigot/vehicle/VehicleDamageEventBoatParityTest.java b/WindSpigot-Server/src/test/java/com/windpvp/windspigot/vehicle/VehicleDamageEventBoatParityTest.java deleted file mode 100644 index 4342e68ec..000000000 --- a/WindSpigot-Server/src/test/java/com/windpvp/windspigot/vehicle/VehicleDamageEventBoatParityTest.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.windpvp.windspigot.vehicle; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; - -import org.bukkit.entity.Entity; -import org.bukkit.entity.Vehicle; -import org.bukkit.event.vehicle.VehicleDamageEvent; -import org.junit.Assert; -import org.junit.Test; - -/** - * Regression for EntityBoat VehicleDamageEvent damage application (TODO #3). - *

- * Boats previously fired and honored cancel, but ignored {@code event.getDamage()}. - * Minecarts already applied modified damage — these tests lock that shared contract. - *

- * Uses JDK proxies instead of Mockito (1.10.19 is incompatible with modern JDKs). - */ -public class VehicleDamageEventBoatParityTest { - - /** Same accumulation used by EntityBoat and EntityMinecartAbstract after the event. */ - private static float applyVehicleHitDamage(float currentDamage, float eventDamage) { - return currentDamage + eventDamage * 10.0F; - } - - @SuppressWarnings("unchecked") - private static T stub(Class type) { - InvocationHandler handler = new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) { - Class r = method.getReturnType(); - if (r == boolean.class) { - return false; - } - if (r == int.class || r == byte.class || r == short.class || r == char.class) { - return 0; - } - if (r == long.class) { - return 0L; - } - if (r == float.class) { - return 0.0F; - } - if (r == double.class) { - return 0.0D; - } - if (r == void.class) { - return null; - } - if (method.getName().equals("equals")) { - return proxy == args[0]; - } - if (method.getName().equals("hashCode")) { - return System.identityHashCode(proxy); - } - if (method.getName().equals("toString")) { - return "stub:" + type.getSimpleName(); - } - return null; - } - }; - return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, handler); - } - - @Test - public void pluginModifiedDamageIsAppliedLikeMinecart() { - Vehicle vehicle = stub(Vehicle.class); - Entity attacker = stub(Entity.class); - - float vanillaHit = 4.0F; - VehicleDamageEvent event = new VehicleDamageEvent(vehicle, attacker, vanillaHit); - event.setDamage(1.5D); - - Assert.assertFalse(event.isCancelled()); - - float f = (float) event.getDamage(); - float boatDamage = applyVehicleHitDamage(0.0F, f); - - Assert.assertEquals(1.5F, f, 0.0001F); - Assert.assertEquals(15.0F, boatDamage, 0.0001F); - // Pre-fix behavior ignored setDamage and would have applied vanillaHit instead: - Assert.assertNotEquals(applyVehicleHitDamage(0.0F, vanillaHit), boatDamage, 0.0001F); - } - - @Test - public void cancelledEventMeansNoDamageApplied() { - Vehicle vehicle = stub(Vehicle.class); - Entity attacker = stub(Entity.class); - - VehicleDamageEvent event = new VehicleDamageEvent(vehicle, attacker, 4.0D); - event.setCancelled(true); - event.setDamage(99.0D); // should be irrelevant when cancelled - - Assert.assertTrue(event.isCancelled()); - // EntityBoat returns false before applying damage when cancelled (SPIGOT-4647). - if (!event.isCancelled()) { - Assert.fail("cancel path must short-circuit before setDamage"); - } - } - - @Test - public void unmodifiedDamageMatchesVanillaHit() { - Vehicle vehicle = stub(Vehicle.class); - - float vanillaHit = 2.0F; - VehicleDamageEvent event = new VehicleDamageEvent(vehicle, null, vanillaHit); - - float f = (float) event.getDamage(); - Assert.assertEquals(vanillaHit, f, 0.0001F); - Assert.assertEquals(20.0F, applyVehicleHitDamage(0.0F, f), 0.0001F); - } -} From 048438f4c2e3e519ac526fb6f362688db0454186 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 14 Oct 2026 20:28:04 +0200 Subject: [PATCH 4/5] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3c5b175d0..9af4b8ddb 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ See the patches list below. [PandaSpigot-0107] Fix GH-276: Item durability desync when some events are cancelled [PandaSpigot-0031] Add missing InventoryView.getSlotType API [PandaSpigot-0104] Backport modern tick loop system +[PandaSpigot-0124] Fix VehicleDamageEvent API [Spigot-0097] Remove DataWatcher Locking by spottedleaf [Spigot-0138] Branchless NibbleArray by md5 From 708f3709d417eb183df448314164ba3dfe4424b1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 14 Oct 2026 21:11:47 +0000 Subject: [PATCH 5/5] fix: apply VehicleDamageEvent damage on boats Co-authored-by: xii69 --- README.md | 1 + .../src/main/java/net/minecraft/server/EntityBoat.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c5b175d0..9af4b8ddb 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ See the patches list below. [PandaSpigot-0107] Fix GH-276: Item durability desync when some events are cancelled [PandaSpigot-0031] Add missing InventoryView.getSlotType API [PandaSpigot-0104] Backport modern tick loop system +[PandaSpigot-0124] Fix VehicleDamageEvent API [Spigot-0097] Remove DataWatcher Locking by spottedleaf [Spigot-0138] Branchless NibbleArray by md5 diff --git a/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java b/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java index 7452a9661..b69bef925 100644 --- a/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java +++ b/WindSpigot-Server/src/main/java/net/minecraft/server/EntityBoat.java @@ -120,7 +120,7 @@ public boolean damageEntity(DamageSource damagesource, float f) { if (event.isCancelled()) { return false; // FalchusSpigot - fix SPIGOT-4647: Properly cancel VehicleDamageEvent } - // f = event.getDamage(); // TODO Why don't we do this? + f = (float) event.getDamage(); // PandaSpigot // CraftBukkit end this.b(-this.m());