diff --git a/paper-api/src/main/java/org/bukkit/entity/Fireball.java b/paper-api/src/main/java/org/bukkit/entity/Fireball.java
index 252e3d35c0..e2974c49db 100644
--- a/paper-api/src/main/java/org/bukkit/entity/Fireball.java
+++ b/paper-api/src/main/java/org/bukkit/entity/Fireball.java
@@ -46,10 +46,6 @@ public interface Fireball extends Projectile, Explosive {
* The acceleration gets applied to the velocity every tick, depending on
* the specific type of the fireball a damping / drag factor is applied so
* that the velocity does not grow into infinity.
- *
- * Note: that the client may not respect non-default acceleration
- * power and will therefore mispredict the location of the fireball, causing
- * visual stutter.
*
* @param acceleration the acceleration
*/
diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch
index d28acac7d5..b651b9bc77 100644
--- a/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch
@@ -28,7 +28,30 @@
this.level = level;
this.broadcast = broadcast;
this.entity = entity;
-@@ -103,16 +_,22 @@
+@@ -98,21 +_,45 @@
+ this.trackedDataValues = entity.getEntityData().getNonDefaultValues();
+ }
+
++ // Paper start - fix desync when a player is added to the tracker
++ public void onPlayerAdd() {
++ // TODO - IMPLEMENT STUBBED METHOD FROM MOONRISE
++ }
++ // Paper end - fix desync when a player is added to the tracker
++ // Paper start - Improve AbstractHurtingProjectile syncing
++ // This can be removed when velocity is no longer downsized when sending it over the protocol.
++ // We create a packet that only updates the velocity on the client.
++ private static final Set RELATIVES = net.minecraft.world.entity.Relative.union(Set.of(net.minecraft.world.entity.Relative.X, net.minecraft.world.entity.Relative.Y, net.minecraft.world.entity.Relative.Z), net.minecraft.world.entity.Relative.ROTATION);
++ public Packet getAccurateVelocityPacket() {
++ return new net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket(this.entity.getId(),
++ new net.minecraft.world.entity.PositionMoveRotation(net.minecraft.world.phys.Vec3.ZERO, this.lastSentMovement, 0, 0),
++ RELATIVES,
++ this.wasOnGround
++ );
++ }
++ // Paper end - Improve AbstractHurtingProjectile syncing
++
+ public void sendChanges() {
+ List passengers = this.entity.getPassengers();
if (!passengers.equals(this.lastPassengers)) {
List list = this.mountedOrDismounted(passengers).map(Entity::getUUID).toList();
this.broadcastWithIgnore.accept(new ClientboundSetPassengersPacket(this.entity), list);
@@ -70,6 +93,15 @@
Packet> packet = null;
boolean flag2 = flag1 || this.tickCount % 60 == 0;
boolean flag3 = false;
+@@ -188,7 +_,7 @@
+ .accept(
+ new ClientboundBundlePacket(
+ List.of(
+- new ClientboundSetEntityMotionPacket(this.entity.getId(), this.lastSentMovement),
++ this.getAccurateVelocityPacket(), // Paper - Improve AbstractHurtingProjectile syncing
+ new ClientboundProjectilePowerPacket(abstractHurtingProjectile.getId(), abstractHurtingProjectile.accelerationPower)
+ )
+ )
@@ -227,6 +_,25 @@
this.tickCount++;
@@ -131,6 +163,20 @@
}
if (!this.entity.getPassengers().isEmpty()) {
+@@ -326,6 +_,13 @@
+ if (this.entity instanceof Leashable leashable && leashable.isLeashed()) {
+ consumer.accept(new ClientboundSetEntityLinkPacket(this.entity, leashable.getLeashHolder()));
+ }
++ // Paper start - Improve AbstractHurtingProjectile syncing
++ // We need to send our more accurate velocity and our acceleration power
++ if (this.entity instanceof AbstractHurtingProjectile abstractHurtingProjectile) {
++ consumer.accept(new ClientboundProjectilePowerPacket(abstractHurtingProjectile.getId(), abstractHurtingProjectile.accelerationPower));
++ consumer.accept(this.getAccurateVelocityPacket());
++ }
++ // Paper end - Improve AbstractHurtingProjectile syncing
+ }
+
+ public Vec3 getPositionBase() {
@@ -359,6 +_,11 @@
if (this.entity instanceof LivingEntity) {
Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();
diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java
index 7e53e12c8e..5d7976f0ad 100644
--- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java
+++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java
@@ -62,9 +62,10 @@ public class CraftFireball extends AbstractProjectile implements Fireball {
public void setAcceleration(@NotNull Vector acceleration) {
Preconditions.checkArgument(acceleration != null, "Vector acceleration cannot be null");
// SPIGOT-6993: AbstractHurtingProjectile#assignDirectionalMovement will normalize the given values
- // Note: Because of MC-80142 the fireball will stutter on the client when setting the power to something other than 0 or the normalized vector * 0.1
- this.getHandle().assignDirectionalMovement(CraftVector.toVec3(acceleration), acceleration.length());
- this.update(); // SPIGOT-6579
+ // Set the acceleration power in order to properly sync the movement to the client
+ double length = acceleration.length();
+ this.getHandle().accelerationPower = length;
+ this.getHandle().assignDirectionalMovement(CraftVector.toVec3(acceleration), length);
}
@NotNull