diff --git a/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/RotationManager.java b/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/RotationManager.java index d25bec4..ef9a9c7 100644 --- a/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/RotationManager.java +++ b/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/RotationManager.java @@ -1,57 +1,39 @@ package dev.loki.lovisual.util.aiming.rotation; -import dev.loki.lovisual.util.aiming.RequestHandler; - -import dev.loki.lovisual.util.screen.ClientScreen; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; -import net.minecraft.network.protocol.game.ServerboundUseItemPacket; -import net.minecraft.util.Mth; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.phys.Vec3; -import dev.loki.lovisual.events.meta.EventHandler; import dev.loki.lovisual.events.Events; +import dev.loki.lovisual.events.impl.input.RotationUpdateEvent; import dev.loki.lovisual.events.impl.network.EventSync; import dev.loki.lovisual.events.impl.network.PacketEvent; import dev.loki.lovisual.events.impl.world.PlayerVelocityStrafe; -import dev.loki.lovisual.events.impl.input.RotationUpdateEvent; +import dev.loki.lovisual.events.meta.EventHandler; import dev.loki.lovisual.mixininterface.entity.ILocalPlayer; import dev.loki.lovisual.mixins.accessors.entity.EntityInvoker; import dev.loki.lovisual.util.aiming.data.Rotation; import dev.loki.lovisual.util.aiming.features.MovementCorrection; +import dev.loki.lovisual.util.aiming.rotation.internal.RotationPackets; +import dev.loki.lovisual.util.aiming.rotation.internal.RotationState; +import dev.loki.lovisual.util.aiming.rotation.internal.RotationStepper; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.phys.Vec3; +/** + * Public rotation facade: singleton entry point, event wiring and accessors. + * The state machine lives in {@link RotationStepper}, state in + * {@link RotationState} and server-rotation tracking in {@link RotationPackets}. + */ public final class RotationManager { public static final RotationManager INSTANCE = new RotationManager(); - private final RequestHandler rotationTargetHandler = new RequestHandler<>(); - private RotationTarget previousRotationTarget; - private Object previousRotationProvider; - - private Rotation currentRotation; - private Rotation previousRotation; - private Object currentRotationProvider; - private boolean smoothReturnActive; - private int smoothReturnTicks; - - private Rotation actualServerRotation = Rotation.ZERO; - private Rotation theoreticalServerRotation = Rotation.ZERO; + private final RotationState state = new RotationState(); + private final RotationPackets packets = new RotationPackets(); private int lastLifecycleAge = Integer.MIN_VALUE; private RotationManager() { } - private static float computeRotationDifference(Rotation a, Rotation b) { - if (a == null || b == null) { - return Float.MAX_VALUE; - } - float yawDiff = Math.abs(RotationUtil.angleDifference(a.yaw(), b.yaw())); - float pitchDiff = Math.abs(a.pitch() - b.pitch()); - return (float) Math.hypot(yawDiff, pitchDiff); - } - public static LocalPlayer player() { return Minecraft.getInstance().player; } @@ -85,78 +67,48 @@ public final class RotationManager { } public RotationTarget getActiveRotationTarget() { - RotationTarget active = rotationTargetHandler.getActiveRequestValue(); - return active != null ? active : previousRotationTarget; + return state.activeTarget(); } public RotationTarget getPreviousRotationTarget() { - return previousRotationTarget; + return state.previousTarget(); } public Rotation getCurrentRotation() { - return currentRotation; - } - - private void setCurrentRotation(Rotation rotation) { - setCurrentRotation(rotation, currentRotationProvider); - } - - private void setCurrentRotation(Rotation rotation, Object provider) { - if (rotation == null) { - if (currentRotation != null) { - previousRotation = currentRotation; - } - currentRotation = null; - currentRotationProvider = null; - return; - } - - previousRotation = currentRotation; - currentRotation = rotation; - currentRotationProvider = provider; + return state.current(); } public Rotation getPreviousRotation() { - return previousRotation; + return state.previous(); } public Rotation getServerRotation() { - return actualServerRotation; + return packets.actual(); } public Rotation getMovementRotation() { LocalPlayer player = player(); + Rotation current = state.current(); Rotation fallback = player != null ? new Rotation(player.getYRot(), player.getXRot(), true) - : (currentRotation != null ? currentRotation : Rotation.ZERO); + : (current != null ? current : Rotation.ZERO); RotationTarget active = getActiveRotationTarget(); - if (currentRotation == null || active == null || active.movementCorrection == MovementCorrection.OFF) { + if (current == null || active == null || active.movementCorrection == MovementCorrection.OFF) { return fallback; } - return currentRotation; + return current; } public void setRotationTarget(RotationTarget plan, int priority, Object provider) { - if (plan == null) return; - - rotationTargetHandler.request(new RequestHandler.Request<>(plan.ticksUntilReset, priority, provider, plan)); - smoothReturnActive = false; - smoothReturnTicks = 0; + state.request(plan, priority, provider); } public void setRotationTarget(RotationTarget plan, int priority) { setRotationTarget(plan, priority, null); } - private boolean isRotatingAllowed(RotationTarget target) { - if (!target.considerInventory) return true; - Minecraft mc = Minecraft.getInstance(); - if (mc == null) return true; - return !(ClientScreen.current() instanceof AbstractContainerScreen); - } - /** * Update current rotation to a new rotation step. */ @@ -167,124 +119,15 @@ public final class RotationManager { return; } - Rotation playerRotation = new Rotation(player.getYRot(), player.getXRot(), true); - RequestHandler.Request activeRequest = rotationTargetHandler.getActiveRequest(); - RotationTarget activeRotationTarget = activeRequest != null ? activeRequest.value() : previousRotationTarget; - Object activeProvider = activeRequest != null ? activeRequest.provider() : previousRotationProvider; - - if (activeRotationTarget == null) { - if (currentRotation == null || !smoothReturnActive) { - return; - } - - float diffToPlayer = computeRotationDifference(currentRotation, playerRotation); - if (diffToPlayer <= 0.5f) { - finishSmoothReturn(); - return; - } - - float speed = 0.25f + 0.4f * Math.min(1.0f, diffToPlayer / 30.0f); - float yawDiff = RotationUtil.angleDifference(playerRotation.yaw(), currentRotation.yaw()); - float newYaw = currentRotation.yaw() + yawDiff * speed; - float newPitch = Mth.lerp(speed, currentRotation.pitch(), playerRotation.pitch()); - setCurrentRotation(new Rotation(newYaw, newPitch, false).normalize(), currentRotationProvider); - return; - } - - if (isRotatingAllowed(activeRotationTarget)) { - Rotation fromRotation = resolveBaseRotation(playerRotation); - if (activeRequest == null) { - if (currentRotation == null) { - finishSmoothReturn(); - return; - } - if (!smoothReturnActive) { - smoothReturnActive = true; - smoothReturnTicks = 0; - } - - Rotation resetRotation = activeRotationTarget.towards(fromRotation, true).normalize(); - setCurrentRotation(resetRotation, activeProvider); - previousRotationTarget = activeRotationTarget; - previousRotationProvider = activeProvider; - - smoothReturnTicks++; - float diffToPlayer = computeRotationDifference(resetRotation, playerRotation); - int maxTicks = Math.max(1, activeRotationTarget.ticksUntilReset); - float threshold = Math.max(0.05f, activeRotationTarget.resetThreshold); - if (diffToPlayer <= threshold || smoothReturnTicks >= maxTicks) { - finishSmoothReturn(); - rotationTargetHandler.tick(); - return; - } - } else { - Rotation rotation = activeRotationTarget.towards(fromRotation, false).normalize(); - setCurrentRotation(rotation, activeProvider); - previousRotationTarget = activeRotationTarget; - previousRotationProvider = activeProvider; - smoothReturnActive = false; - smoothReturnTicks = 0; - - if (activeRotationTarget.whenReached != null) { - activeRotationTarget.whenReached.invoke(); - } - } - } - - rotationTargetHandler.tick(); - } - - private void finishSmoothReturn() { - setCurrentRotation(null); - previousRotation = null; - previousRotationTarget = null; - previousRotationProvider = null; - smoothReturnActive = false; - smoothReturnTicks = 0; - } - - private Rotation resolveBaseRotation(Rotation playerRotation) { - if (currentRotation != null) { - return currentRotation; - } - if (actualServerRotation != null && actualServerRotation != Rotation.ZERO) { - return actualServerRotation; - } - return playerRotation; + RotationStepper.step(state, player, packets.actual()); } public void clear() { - rotationTargetHandler.clear(); - previousRotationTarget = null; - previousRotationProvider = null; - currentRotation = null; - currentRotationProvider = null; - previousRotation = null; - smoothReturnActive = false; - smoothReturnTicks = 0; + state.clearAll(); } public void clear(Object provider) { - if (provider == null) return; - boolean removed = rotationTargetHandler.clear(provider); - boolean ownsVisualState = provider == currentRotationProvider || provider == previousRotationProvider; - if (!removed && !ownsVisualState) { - return; - } - - if (currentRotation != null && ownsVisualState) { - smoothReturnActive = true; - smoothReturnTicks = 0; - return; - } - - if (ownsVisualState) { - previousRotationTarget = null; - previousRotationProvider = null; - previousRotation = null; - } - smoothReturnActive = false; - smoothReturnTicks = 0; + state.clearProvider(provider); } /** @@ -296,33 +139,16 @@ public final class RotationManager { } public void release(Object provider, boolean smoothReturn) { - if (provider == null) return; - boolean removed = rotationTargetHandler.clear(provider); - boolean ownsVisualState = provider == currentRotationProvider || provider == previousRotationProvider; - if (!removed && !ownsVisualState) { - return; - } - if (!smoothReturn) { - if (ownsVisualState) { - setCurrentRotation(null); - previousRotationTarget = null; - previousRotationProvider = null; - previousRotation = null; - } - smoothReturnActive = false; - smoothReturnTicks = 0; - return; - } - smoothReturnActive = ownsVisualState && currentRotation != null; - smoothReturnTicks = 0; + state.releaseProvider(provider, smoothReturn); } @EventHandler public void onSync(EventSync e) { - RotationTarget activeRotationTarget = getActiveRotationTarget(); - if (activeRotationTarget == null || currentRotation == null) return; + RotationTarget active = state.activeTarget(); + Rotation current = state.current(); + if (active == null || current == null) return; - e.setRotation(currentRotation.yaw(), currentRotation.pitch(), true); + e.setRotation(current.yaw(), current.pitch(), true); } @EventHandler(priority = 100) @@ -346,30 +172,12 @@ public final class RotationManager { @EventHandler(priority = -100) public void onPacketSend(PacketEvent.Send e) { - if (e.isCancelled()) return; - var packet = e.getPacket(); - - Rotation rot = null; - if (packet instanceof ServerboundMovePlayerPacket move) { - if (!move.hasRotation()) return; - rot = new Rotation(move.getYRot(0.0f), move.getXRot(0.0f), true); - } else if (packet instanceof ServerboundUseItemPacket use) { - rot = new Rotation(use.getYRot(), use.getXRot(), true); - } - - if (rot != null) { - actualServerRotation = rot; - theoreticalServerRotation = rot; - } + packets.onSend(e); } @EventHandler(priority = -100) public void onPacketReceive(PacketEvent.Receive e) { - if (!(e.getPacket() instanceof net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket packet)) - return; - Rotation rot = new Rotation(packet.change().yRot(), packet.change().xRot(), true); - theoreticalServerRotation = rot; - actualServerRotation = rot; + packets.onReceive(e); } public void runTickLifecycle() { diff --git a/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/internal/RotationPackets.java b/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/internal/RotationPackets.java new file mode 100644 index 0000000..e5d7482 --- /dev/null +++ b/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/internal/RotationPackets.java @@ -0,0 +1,50 @@ +package dev.loki.lovisual.util.aiming.rotation.internal; + +import dev.loki.lovisual.events.impl.network.PacketEvent; +import dev.loki.lovisual.util.aiming.data.Rotation; +import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import net.minecraft.network.protocol.game.ServerboundUseItemPacket; + +/** + * Tracks the rotation the server sees, updated from outbound and inbound + * movement packets. Kept apart from the facade so packet wiring stays small. + */ +public final class RotationPackets { + + private Rotation actualServerRotation = Rotation.ZERO; + private Rotation theoreticalServerRotation = Rotation.ZERO; + + public Rotation actual() { + return actualServerRotation; + } + + public void onSend(PacketEvent.Send e) { + if (e.isCancelled()) return; + var packet = e.getPacket(); + + Rotation rot = null; + if (packet instanceof ServerboundMovePlayerPacket move) { + if (!move.hasRotation()) return; + rot = new Rotation(move.getYRot(0.0f), move.getXRot(0.0f), true); + } else if (packet instanceof ServerboundUseItemPacket use) { + rot = new Rotation(use.getYRot(), use.getXRot(), true); + } + + if (rot != null) { + actualServerRotation = rot; + theoreticalServerRotation = rot; + } + } + + public void onReceive(PacketEvent.Receive e) { + if (!(e.getPacket() instanceof ClientboundPlayerPositionPacket packet)) return; + Rotation rot = new Rotation(packet.change().yRot(), packet.change().xRot(), true); + theoreticalServerRotation = rot; + actualServerRotation = rot; + } + + public Rotation theoretical() { + return theoreticalServerRotation; + } +} diff --git a/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/internal/RotationState.java b/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/internal/RotationState.java new file mode 100644 index 0000000..cd253f9 --- /dev/null +++ b/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/internal/RotationState.java @@ -0,0 +1,128 @@ +package dev.loki.lovisual.util.aiming.rotation.internal; + +import dev.loki.lovisual.util.aiming.RequestHandler; +import dev.loki.lovisual.util.aiming.data.Rotation; +import dev.loki.lovisual.util.aiming.rotation.RotationTarget; + +/** + * Mutable rotation state shared by {@link RotationStepper} and the public + * {@code RotationManager} facade. Fields are package-private on purpose so the + * stepper can drive them in place, mirroring the original single-class logic. + */ +public final class RotationState { + + final RequestHandler handler = new RequestHandler<>(); + + RotationTarget previousTarget; + Object previousProvider; + Rotation current; + Rotation previous; + Object currentProvider; + boolean smoothReturnActive; + int smoothReturnTicks; + + public RotationTarget activeTarget() { + RotationTarget active = handler.getActiveRequestValue(); + return active != null ? active : previousTarget; + } + + public RotationTarget previousTarget() { + return previousTarget; + } + + public Rotation current() { + return current; + } + + public Rotation previous() { + return previous; + } + + public void request(RotationTarget plan, int priority, Object provider) { + if (plan == null) return; + + handler.request(new RequestHandler.Request<>(plan.ticksUntilReset, priority, provider, plan)); + smoothReturnActive = false; + smoothReturnTicks = 0; + } + + void setCurrent(Rotation rotation, Object provider) { + if (rotation == null) { + if (current != null) { + previous = current; + } + current = null; + currentProvider = null; + return; + } + + previous = current; + current = rotation; + currentProvider = provider; + } + + void finishSmoothReturn() { + setCurrent(null, currentProvider); + previous = null; + previousTarget = null; + previousProvider = null; + smoothReturnActive = false; + smoothReturnTicks = 0; + } + + public void clearAll() { + handler.clear(); + previousTarget = null; + previousProvider = null; + current = null; + currentProvider = null; + previous = null; + smoothReturnActive = false; + smoothReturnTicks = 0; + } + + public void clearProvider(Object provider) { + if (provider == null) return; + boolean removed = handler.clear(provider); + boolean ownsVisualState = provider == currentProvider || provider == previousProvider; + if (!removed && !ownsVisualState) { + return; + } + + if (current != null && ownsVisualState) { + smoothReturnActive = true; + smoothReturnTicks = 0; + return; + } + + if (ownsVisualState) { + previousTarget = null; + previousProvider = null; + previous = null; + } + smoothReturnActive = false; + smoothReturnTicks = 0; + } + + public void releaseProvider(Object provider, boolean smoothReturn) { + if (provider == null) return; + boolean removed = handler.clear(provider); + boolean ownsVisualState = provider == currentProvider || provider == previousProvider; + if (!removed && !ownsVisualState) { + return; + } + if (!smoothReturn) { + if (ownsVisualState) { + setCurrent(null, currentProvider); + previousTarget = null; + previousProvider = null; + previous = null; + } + smoothReturnActive = false; + smoothReturnTicks = 0; + return; + } + smoothReturnActive = ownsVisualState && current != null; + smoothReturnTicks = 0; + } +} diff --git a/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/internal/RotationStepper.java b/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/internal/RotationStepper.java new file mode 100644 index 0000000..99996f6 --- /dev/null +++ b/mod/src/main/java/dev/loki/lovisual/util/aiming/rotation/internal/RotationStepper.java @@ -0,0 +1,115 @@ +package dev.loki.lovisual.util.aiming.rotation.internal; + +import dev.loki.lovisual.util.aiming.RequestHandler; +import dev.loki.lovisual.util.aiming.data.Rotation; +import dev.loki.lovisual.util.aiming.rotation.RotationTarget; +import dev.loki.lovisual.util.aiming.rotation.RotationUtil; +import dev.loki.lovisual.util.screen.ClientScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.util.Mth; + +/** + * Per-tick rotation state machine: resolves the active target, advances the + * current rotation and drives the smooth return back to the player view. + */ +public final class RotationStepper { + + private RotationStepper() { + } + + public static void step(RotationState state, LocalPlayer player, Rotation serverRotation) { + Rotation playerRotation = new Rotation(player.getYRot(), player.getXRot(), true); + RequestHandler.Request activeRequest = state.handler.getActiveRequest(); + RotationTarget activeRotationTarget = activeRequest != null ? activeRequest.value() : state.previousTarget; + Object activeProvider = activeRequest != null ? activeRequest.provider() : state.previousProvider; + + if (activeRotationTarget == null) { + if (state.current == null || !state.smoothReturnActive) { + return; + } + + float diffToPlayer = computeRotationDifference(state.current, playerRotation); + if (diffToPlayer <= 0.5f) { + state.finishSmoothReturn(); + return; + } + + float speed = 0.25f + 0.4f * Math.min(1.0f, diffToPlayer / 30.0f); + float yawDiff = RotationUtil.angleDifference(playerRotation.yaw(), state.current.yaw()); + float newYaw = state.current.yaw() + yawDiff * speed; + float newPitch = Mth.lerp(speed, state.current.pitch(), playerRotation.pitch()); + state.setCurrent(new Rotation(newYaw, newPitch, false).normalize(), state.currentProvider); + return; + } + + if (isRotatingAllowed(activeRotationTarget)) { + Rotation fromRotation = resolveBaseRotation(state, playerRotation, serverRotation); + if (activeRequest == null) { + if (state.current == null) { + state.finishSmoothReturn(); + return; + } + if (!state.smoothReturnActive) { + state.smoothReturnActive = true; + state.smoothReturnTicks = 0; + } + + Rotation resetRotation = activeRotationTarget.towards(fromRotation, true).normalize(); + state.setCurrent(resetRotation, activeProvider); + state.previousTarget = activeRotationTarget; + state.previousProvider = activeProvider; + + state.smoothReturnTicks++; + float diffToPlayer = computeRotationDifference(resetRotation, playerRotation); + int maxTicks = Math.max(1, activeRotationTarget.ticksUntilReset); + float threshold = Math.max(0.05f, activeRotationTarget.resetThreshold); + if (diffToPlayer <= threshold || state.smoothReturnTicks >= maxTicks) { + state.finishSmoothReturn(); + state.handler.tick(); + return; + } + } else { + Rotation rotation = activeRotationTarget.towards(fromRotation, false).normalize(); + state.setCurrent(rotation, activeProvider); + state.previousTarget = activeRotationTarget; + state.previousProvider = activeProvider; + state.smoothReturnActive = false; + state.smoothReturnTicks = 0; + + if (activeRotationTarget.whenReached != null) { + activeRotationTarget.whenReached.invoke(); + } + } + } + + state.handler.tick(); + } + + private static float computeRotationDifference(Rotation a, Rotation b) { + if (a == null || b == null) { + return Float.MAX_VALUE; + } + float yawDiff = Math.abs(RotationUtil.angleDifference(a.yaw(), b.yaw())); + float pitchDiff = Math.abs(a.pitch() - b.pitch()); + return (float) Math.hypot(yawDiff, pitchDiff); + } + + private static boolean isRotatingAllowed(RotationTarget target) { + if (!target.considerInventory) return true; + Minecraft mc = Minecraft.getInstance(); + if (mc == null) return true; + return !(ClientScreen.current() instanceof AbstractContainerScreen); + } + + private static Rotation resolveBaseRotation(RotationState state, Rotation playerRotation, Rotation serverRotation) { + if (state.current != null) { + return state.current; + } + if (serverRotation != null && serverRotation != Rotation.ZERO) { + return serverRotation; + } + return playerRotation; + } +}