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)) }) + } +}