Fix mount movement jitter by following vanilla horse/boat sync pattern

- Remove all velocityChanged=true flags from mount movement code
  (NPCMountUtil, EntityNPCInterface, EntityNPCFlying)
- Make performMountedMovement physics server-side only like vanilla horse
- Remove syncMountedRiderVelocity method that forced rider velocity packets
- Fix ground movement to not reset motionY every tick
- Let Minecraft's entity tracker handle position sync naturally

The jitter was caused by S12PacketEntityVelocity being sent every tick,
which constantly reset client interpolation. Vanilla horses/boats never
set velocityChanged and rely on the entity tracker for smooth sync.
This commit is contained in:
Claude committed 2025-12-30 23:06:16 +00:00
1 parent f3d39abfaf
commit 096c9b0a69
3 files changed
+11 -96

No files matched your search

@@ -84,7 +84,6 @@ public abstract class EntityNPCFlying extends EntityNPCInterface {
}
this.updateLimbSwing();
this.velocityChanged = true;
}
public void updateLimbSwing() {
@@ -136,7 +135,6 @@ public abstract class EntityNPCFlying extends EntityNPCInterface {
if (flightModeActive) {
super.performMountedMovement(strafe, forward, moveSpeed);
this.velocityChanged = true;
return;
}
@@ -149,6 +147,5 @@ public abstract class EntityNPCFlying extends EntityNPCInterface {
}
this.updateLimbSwing();
this.velocityChanged = true;
}
}
@@ -2130,35 +2130,21 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti
this.moveForward = forward;
boolean flightMode = isMountFlightModeActive();
double prevMotionX = this.motionX;
double prevMotionY = this.motionY;
double prevMotionZ = this.motionZ;
float previousJumpFactor = this.jumpMovementFactor;
float appliedJumpFactor = getMountedAirStrafeFactor(moveSpeed, flightMode);
this.jumpMovementFactor = appliedJumpFactor;
this.setAIMoveSpeed(moveSpeed);
super.moveEntityWithHeading(strafe, forward);
if (flightMode) {
this.motionY = prevMotionY;
this.isAirBorne = true;
}
this.jumpMovementFactor = previousJumpFactor;
// Server-side only physics (like vanilla horse)
if (!worldObj.isRemote) {
double newMotionX = this.motionX;
double newMotionY = this.motionY;
double newMotionZ = this.motionZ;
if (Math.abs(newMotionX - prevMotionX) > 1.0E-5D || Math.abs(newMotionY - prevMotionY) > 1.0E-5D || Math.abs(newMotionZ - prevMotionZ) > 1.0E-5D) {
this.velocityChanged = true;
this.jumpMovementFactor = getMountedAirStrafeFactor(moveSpeed, flightMode);
this.setAIMoveSpeed(moveSpeed);
super.moveEntityWithHeading(strafe, forward);
if (flightMode) {
this.motionY = prevMotionY;
this.isAirBorne = true;
}
}
syncMountedRiderVelocity();
// Both sides do animation (like vanilla horse)
updateMountedLimbSwing();
}
@@ -2176,28 +2162,6 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti
return moveSpeed * 0.5F;
}
private void syncMountedRiderVelocity() {
if (!(this.riddenByEntity instanceof EntityLivingBase)) {
return;
}
EntityLivingBase rider = (EntityLivingBase) this.riddenByEntity;
double riderPrevMotionX = rider.motionX;
double riderPrevMotionY = rider.motionY;
double riderPrevMotionZ = rider.motionZ;
rider.motionX = this.motionX;
rider.motionY = this.motionY;
rider.motionZ = this.motionZ;
rider.fallDistance = 0.0F;
rider.isAirBorne = !this.onGround;
if (!worldObj.isRemote) {
if (Math.abs(rider.motionX - riderPrevMotionX) > 1.0E-5D || Math.abs(rider.motionY - riderPrevMotionY) > 1.0E-5D || Math.abs(rider.motionZ - riderPrevMotionZ) > 1.0E-5D) {
rider.velocityChanged = true;
}
}
}
private void updateMountedLimbSwing() {
this.prevLimbSwingAmount = this.limbSwingAmount;
double deltaX = this.posX - this.prevPosX;
@@ -122,49 +122,22 @@ public final class NPCMountUtil {
npc.isAirBorne = true;
npc.fallDistance = 0.0F;
if (!npc.worldObj.isRemote && Math.abs(previousMotionY - newMotionY) > 1.0E-4D) {
npc.velocityChanged = true;
}
if (!npc.worldObj.isRemote && rider instanceof EntityPlayer) {
EntityPlayer player = (EntityPlayer) rider;
if (!player.capabilities.isFlying) {
double playerPrevMotion = player.motionY;
player.motionY = npc.motionY;
if (Math.abs(playerPrevMotion - player.motionY) > 1.0E-4D) {
player.velocityChanged = true;
}
}
}
return;
}
if (jumpPressed && npc.onGround) {
double previousMotionY = npc.motionY;
npc.motionY = jumpFactor;
npc.setNpcJumpingState(true);
npc.isAirBorne = true;
npc.fallDistance = 0.0F;
if (!npc.worldObj.isRemote) {
if (Math.abs(previousMotionY - npc.motionY) > 1.0E-4D) {
npc.velocityChanged = true;
}
net.minecraftforge.common.ForgeHooks.onLivingJump(npc);
}
return;
}
if (!npc.onGround) {
double previousMotionY = npc.motionY;
double limitedMotionY = Math.max(npc.motionY, -descendSpeed);
if (Math.abs(previousMotionY - limitedMotionY) > 1.0E-4D) {
npc.motionY = limitedMotionY;
if (!npc.worldObj.isRemote) {
npc.velocityChanged = true;
}
} else {
npc.motionY = limitedMotionY;
}
npc.motionY = Math.max(npc.motionY, -descendSpeed);
npc.setNpcFlyingState(!npc.isInWater());
} else {
npc.setNpcFlyingState(false);
@@ -174,20 +147,14 @@ public final class NPCMountUtil {
}
if (npc.onGround) {
double previousMotionY = npc.motionY;
if (rider.isJumping) {
npc.motionY = jumpFactor;
npc.setNpcJumpingState(true);
npc.isAirBorne = true;
npc.fallDistance = 0.0F;
if (!npc.worldObj.isRemote) {
if (Math.abs(previousMotionY - npc.motionY) > 1.0E-4D) {
npc.velocityChanged = true;
}
net.minecraftforge.common.ForgeHooks.onLivingJump(npc);
}
} else {
npc.motionY = 0.0D;
}
return;
}
@@ -239,9 +206,6 @@ public final class NPCMountUtil {
}
public static void haltMountedMotion(EntityNPCInterface npc, MountState state) {
double prevMotionX = npc.motionX;
double prevMotionY = npc.motionY;
double prevMotionZ = npc.motionZ;
npc.motionX = 0.0D;
npc.motionY = 0.0D;
npc.motionZ = 0.0D;
@@ -254,9 +218,6 @@ public final class NPCMountUtil {
npc.limbSwing = 0.0F;
npc.getNavigator().clearPathEntity();
npc.fallDistance = 0.0F;
if (!npc.worldObj.isRemote && (Math.abs(prevMotionX) > 1.0E-4D || Math.abs(prevMotionY) > 1.0E-4D || Math.abs(prevMotionZ) > 1.0E-4D)) {
npc.velocityChanged = true;
}
resetMountedFlightState(npc, state);
}
@@ -283,16 +244,11 @@ public final class NPCMountUtil {
return;
}
double desired = -descendSpeed;
double previousMotionY = npc.motionY;
npc.motionY = desired;
npc.motionY = -descendSpeed;
npc.isAirBorne = true;
npc.fallDistance = 0.0F;
npc.setNpcFlyingState(true);
npc.setNpcJumpingState(false);
if (!npc.worldObj.isRemote && Math.abs(previousMotionY - desired) > 1.0E-4D) {
npc.velocityChanged = true;
}
}
public static void stabilizeDismountedRider(Entity rider) {
@@ -314,8 +270,6 @@ public final class NPCMountUtil {
rider.motionZ = previousMotionZ;
rider.fallDistance = previousFall;
}
rider.velocityChanged = true;
}
private static void updateMountedFlightState(EntityNPCInterface npc, MountState state, RoleMount mount, EntityPlayer rider) {