diff --git a/src/main/java/com/zenith/event/client/BotPrePhysicsTick.java b/src/main/java/com/zenith/event/client/BotPrePhysicsTick.java new file mode 100644 index 000000000..699cffc25 --- /dev/null +++ b/src/main/java/com/zenith/event/client/BotPrePhysicsTick.java @@ -0,0 +1,9 @@ +package com.zenith.event.client; + +// Posted once per bot tick, after the bot has applied the server's latest entity metadata sync +// (e.g. fall-flying/pose/abilities) for this tick, but before the bot's local physics/movement step runs. +// Intended for plugins that need to influence tick-local physics-affecting state (e.g. fall-flying) at this +// exact point, so that client-side prediction stays consistent with what the server confirms a moment later. +public record BotPrePhysicsTick() { + public static final BotPrePhysicsTick INSTANCE = new BotPrePhysicsTick(); +} diff --git a/src/main/java/com/zenith/feature/player/Bot.java b/src/main/java/com/zenith/feature/player/Bot.java index 0547f5570..610a68e33 100644 --- a/src/main/java/com/zenith/feature/player/Bot.java +++ b/src/main/java/com/zenith/feature/player/Bot.java @@ -6,6 +6,7 @@ import com.zenith.cache.data.entity.EntityLiving; import com.zenith.cache.data.entity.EntityPlayer; import com.zenith.cache.data.inventory.Container; +import com.zenith.event.client.BotPrePhysicsTick; import com.zenith.event.client.ClientBotTick; import com.zenith.event.client.ClientTickEvent; import com.zenith.feature.spectator.SpectatorSync; @@ -374,6 +375,12 @@ && canPlayerFitWithinBlocksAndEntitiesWhen(getCollisionBox(Pose.SNEAKING)) if (movementInput.jumping && !didUpdateFlyState && !wasJumpPressed && !onClimbable() && tryToStartFallFlying()) { sendClientPacketAsync(new ServerboundPlayerCommandPacket(CACHE.getPlayerCache().getEntityId(), PlayerState.START_ELYTRA_FLYING)); } + // Extension point for plugins: the server's latest entity metadata for this tick (fall-flying included) + // has just been applied above via updateFallFlying(), but the physics/movement step below hasn't run yet. + // Plugins implementing their own movement/flight logic can hook this to adjust tick-local physics-affecting + // state (e.g. via setFallFlying) at the one point in the tick where doing so stays consistent with what + // the server will confirm next, instead of racing the next metadata sync. + EVENT_BUS.post(BotPrePhysicsTick.INSTANCE); if (isFlying) { int verticalDirection = 0; if (this.movementInput.isSneaking()) { @@ -1746,6 +1753,16 @@ boolean tryToStartFallFlying() { } } + // Public entry point for plugins (e.g. via a BotPrePhysicsTick subscriber) to set local fall-flying state + // directly, without going through tryToStartFallFlying()'s canGlide() gating. + public void setFallFlying(boolean value) { + if (value) { + startFallFlying(); + } else { + stopFallFlying(); + } + } + void startFallFlying() { var metadata0 = CACHE.getPlayerCache().getThePlayer().getMetadata().get(0); if (metadata0 instanceof ByteEntityMetadata bmd0) {