Improve AbstractHurtingProjectile syncing

I am sure this has been reported before but I cannot search for any bugs due to Mojang's new bug tracker.

Projectiles at high velocities very obviously go off track from their original course when sent to the client. This is due to the aggressive downsizing of velocity when sent over the client, and these entities typically have large velocity and still work just fine. We workaround this by sending the velocity for these entities exactly by using a teleport packet with relative flags and zero positional data. This prevents any possible position syncing, but sends an accurate velocity.

Then, we properly send the ClientboundProjectilePowerPacket on the first tick the entity is created, as vanilla only updates this when the delta is updated typically.
This commit is contained in:
Owen1212055
2025-06-23 22:36:49 -04:00
parent 10c7914592
commit 068a1ccda1
3 changed files with 51 additions and 8 deletions

View File

@@ -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.
* <br>
* <b>Note:</b> 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
*/

View File

@@ -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<net.minecraft.world.entity.Relative> 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<ClientGamePacketListener> 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<Entity> passengers = this.entity.getPassengers();
if (!passengers.equals(this.lastPassengers)) {
List<UUID> 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<AttributeInstance> attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync();

View File

@@ -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