diff --git a/src/main/java/com/lambda/mixin/input/MouseMixin.java b/src/main/java/com/lambda/mixin/input/MouseMixin.java
index b6b1df9b2..9802da2de 100644
--- a/src/main/java/com/lambda/mixin/input/MouseMixin.java
+++ b/src/main/java/com/lambda/mixin/input/MouseMixin.java
@@ -19,12 +19,16 @@
import com.lambda.event.EventFlow;
import com.lambda.event.events.MouseEvent;
+import com.lambda.module.modules.render.Zoom;
import com.lambda.util.math.Vec2d;
import net.minecraft.client.Mouse;
+import net.minecraft.client.option.GameOptions;
+import net.minecraft.client.option.SimpleOption;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Mouse.class)
@@ -61,4 +65,17 @@ private void onCursorPos(long window, double x, double y, CallbackInfo ci) {
ci.cancel();
}
}
+
+ @Redirect(method = "updateMouse", at = @At(value = "FIELD", target = "Lnet/minecraft/client/option/GameOptions;smoothCameraEnabled:Z"))
+ private boolean redirectSmoothCameraEnabled(GameOptions instance) {
+ if (Zoom.INSTANCE.isEnabled() && Zoom.getSmoothMovement()) return true;
+ else return instance.smoothCameraEnabled;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Redirect(method = "updateMouse", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/option/SimpleOption;getValue()Ljava/lang/Object;", ordinal = 0))
+ private Object redirectGetValue(SimpleOption instance) {
+ if (Zoom.INSTANCE.isEnabled()) return ((Double) instance.getValue()) / Zoom.getTargetZoom();
+ else return instance.getValue();
+ }
}
diff --git a/src/main/java/com/lambda/mixin/render/GameRendererMixin.java b/src/main/java/com/lambda/mixin/render/GameRendererMixin.java
index 48afd23af..8cf2205da 100644
--- a/src/main/java/com/lambda/mixin/render/GameRendererMixin.java
+++ b/src/main/java/com/lambda/mixin/render/GameRendererMixin.java
@@ -21,7 +21,9 @@
import com.lambda.event.events.RenderEvent;
import com.lambda.graphics.RenderMain;
import com.lambda.module.modules.render.NoRender;
+import com.lambda.module.modules.render.Zoom;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.render.Camera;
@@ -73,4 +75,9 @@ private float modifyMax(float original) {
private void injectShowFloatingItem(ItemStack floatingItem, CallbackInfo ci) {
if (NoRender.INSTANCE.isEnabled() && NoRender.getNoFloatingItemAnimation()) ci.cancel();
}
+
+ @ModifyReturnValue(method = "getFov", at = @At("RETURN"))
+ private float modifyGetFov(float original) {
+ return original / Zoom.getCurrentZoom();
+ }
}
diff --git a/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt b/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt
new file mode 100644
index 000000000..f37520e9a
--- /dev/null
+++ b/src/main/kotlin/com/lambda/module/modules/render/Zoom.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2025 Lambda
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.lambda.module.modules.render
+
+import com.lambda.event.events.MouseEvent
+import com.lambda.event.events.RenderEvent
+import com.lambda.event.listener.SafeListener.Companion.listen
+import com.lambda.module.Module
+import com.lambda.module.tag.ModuleTag
+import com.lambda.util.NamedEnum
+import java.lang.Math.clamp
+
+object Zoom : Module(
+ name = "Zoom",
+ description = "Zooms the current view",
+ tag = ModuleTag.RENDER,
+) {
+ private var zoom by setting("Zoom", 2f, 1f..10f, 0.1f)
+ private val style by setting("Style", ZoomStyle.EaseOut)
+ private val animationDuration by setting("Animation Duration", 1f, 0.1f..10f, 0.1f) { style != ZoomStyle.Instant }
+ private val scroll by setting("Scroll", true)
+ private val persistentScroll by setting("Persistent Scroll", false) { scroll }
+ private val sensitivity by setting("Sensitivity", 0.2f, 0.1f..1f, 0.1f) { scroll }
+ @JvmStatic val smoothMovement by setting("Smooth Movement", false)
+
+ private var extraZoom = 0f
+ set(value) {
+ field = value.coerceAtLeast(-zoom + 1)
+ }
+ @JvmStatic val targetZoom: Float
+ get() = zoom + extraZoom
+
+ @JvmStatic var currentZoom = 1f; private set
+ private var lastZoomTime = 1L
+ private val zoomProgress
+ get() = clamp((System.currentTimeMillis() - lastZoomTime) / (animationDuration * 1000).toDouble(), 0.0, 1.0).toFloat()
+
+ init {
+ listen { event ->
+ val yDelta = event.delta.y.toFloat()
+ val delta = (yDelta * sensitivity) + (((zoom + extraZoom) * sensitivity) * yDelta)
+ if (persistentScroll) zoom += delta
+ else extraZoom += delta
+ updateZoomTime()
+ event.cancel()
+ }
+
+ listen(alwaysListen = true) {
+ updateCurrentZoom()
+ }
+
+ onEnable {
+ updateZoomTime()
+ }
+
+ onDisable {
+ extraZoom = 0f
+ updateZoomTime()
+ }
+ }
+
+ private fun updateZoomTime() {
+ lastZoomTime = System.currentTimeMillis()
+ }
+
+ @JvmStatic
+ fun updateCurrentZoom() {
+ val target = if (isEnabled) targetZoom else 1f
+ currentZoom = style.apply(currentZoom, target, zoomProgress)
+ }
+
+ private enum class ZoomStyle(
+ override val displayName: String,
+ val apply: (Float, Float, Float) -> Float,
+ ) : NamedEnum {
+ Instant("Instant", { _, v, _ -> v }),
+ EaseOut("Ease Out", { start, end, progress -> start + ((end - start) * (1f - ((1f - progress) * (1f - progress)))) }),
+ EaseIn("Ease In", { start, end, progress -> start + ((end - start) * (progress * progress)) })
+ }
+}