Freelook zoom

This commit is contained in:
2026-03-26 16:26:49 +01:00
parent 3e4985c6bc
commit 7eb8b6a120
@@ -1,49 +1,59 @@
package org.saturnclient.mod.mods; package org.saturnclient.mod.mods;
import java.util.function.Function;
import org.saturnclient.common.provider.Providers; import org.saturnclient.common.provider.Providers;
import org.saturnclient.common.provider.GLFWProvider;
import org.saturnclient.config.property.BoolProperty; import org.saturnclient.config.property.BoolProperty;
import org.saturnclient.config.property.FloatProperty;
import org.saturnclient.config.property.IntProperty;
import org.saturnclient.config.property.KeybindingProperty; import org.saturnclient.config.property.KeybindingProperty;
import org.saturnclient.config.property.Property; import org.saturnclient.config.property.Property;
import org.saturnclient.config.property.SelectProperty;
import org.saturnclient.mod.Mod; import org.saturnclient.mod.Mod;
import org.saturnclient.mod.ModSpec; import org.saturnclient.mod.ModSpec;
import org.saturnclient.common.feature.RenderFeature; import org.saturnclient.common.feature.RenderFeature;
import org.saturnclient.common.provider.GLFWProvider; import org.saturnclient.ui.anim.Animation;
/**
* FreelookMod lets the player look around freely (in third-person)
* without changing the direction their character walks.
*
* Camera perspective is controlled through {@link RenderFeature} so
* that the feature itself contains no direct Minecraft API calls.
*/
public class FreelookMod extends Mod { public class FreelookMod extends Mod {
private static final BoolProperty enabled = Property.bool(false); private static final BoolProperty enabled = Property.bool(false);
private static final BoolProperty toggle = Property.bool(true); private static final BoolProperty toggle = Property.bool(true);
public static final KeybindingProperty freelookKey = Property.keybinding(GLFWProvider.GLFW_KEY_H); public static final KeybindingProperty freelookKey = Property.keybinding(GLFWProvider.GLFW_KEY_H);
/** Whether freelook is currently active (read by the camera mixin). */ /** Zoom properties for freelook */
public static final FloatProperty zoomLevel = Property.floatProp(2.0f); // 2x zoom out
public static final IntProperty zoomInDuration = Property.integer(500); // ms
public static final IntProperty zoomOutDuration = Property.integer(300); // ms
public static final SelectProperty curve = Property.select(1, "Ease In Out Cubic", "Ease Out Cubic",
"Ease In Out Back");
/** Whether freelook is currently active */
public static boolean isFreeLooking = false; public static boolean isFreeLooking = false;
private static boolean wasFirstPerson = false; private static boolean wasFirstPerson = false;
/** Animation state */
private static long animationStartTime = System.currentTimeMillis();
public FreelookMod() { public FreelookMod() {
super( super(
new ModSpec("Freelook", "freelook") new ModSpec("Freelook", "freelook")
.description("Look around freely without moving your character") .description("Look around freely without moving your character")
.tags("Movement", "Camera") .tags("Movement", "Camera")
.version("v0.2.0") .version("v0.3.1")
.requires(Providers.feature::render), .requires(Providers.feature::render),
enabled.named("Enabled"), enabled.named("Enabled"),
toggle.named("Toggle freelook"), toggle.named("Toggle freelook"),
freelookKey.named("Freelook Keybinding")); freelookKey.named("Freelook Keybinding"),
zoomLevel.named("Zoom Level"),
zoomInDuration.named("Zoom In Duration"),
zoomOutDuration.named("Zoom Out Duration"),
curve.named("Curve"));
} }
// ---------------------------------------------------------------
// Mod contract
// ---------------------------------------------------------------
@Override @Override
public void tick() { public void tick() {
// Only update freelook state — do NOT touch animation here
if (toggle.value) { if (toggle.value) {
if (freelookKey.wasKeyPressed()) { if (freelookKey.wasKeyPressed()) {
if (isFreeLooking) if (isFreeLooking)
@@ -71,15 +81,12 @@ public class FreelookMod extends Mod {
return enabled.value; return enabled.value;
} }
// ---------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------
private void startFreelook() { private void startFreelook() {
RenderFeature render = Providers.feature.render(); RenderFeature render = Providers.feature.render();
wasFirstPerson = render.isFirstPerson(); wasFirstPerson = render.isFirstPerson();
render.setThirdPersonBack(); render.setThirdPersonBack();
isFreeLooking = true; isFreeLooking = true;
animationStartTime = System.currentTimeMillis();
} }
private void stopFreelook() { private void stopFreelook() {
@@ -87,5 +94,43 @@ public class FreelookMod extends Mod {
Providers.feature.render().setFirstPerson(); Providers.feature.render().setFirstPerson();
} }
isFreeLooking = false; isFreeLooking = false;
animationStartTime = System.currentTimeMillis();
} }
}
/** Returns true if we should apply zoom out effect */
public static boolean shouldZoomOut() {
return enabled.value && (isFreeLooking || getProgress() > 0f);
}
/** Returns the current zoom divisor (1 = normal, >1 = zoomed out) */
public static float getZoomLevel() {
float progress = getProgress();
// Apply curve
Function<Double, Double> curveFn = Animation.getCurve(curve.value);
double curved = curveFn.apply((double) progress);
// Map 0 → zoomLevel, 1 → normal FOV
return zoomLevel.value - (float) curved * (zoomLevel.value - 1f);
}
/** Returns animation progress 0 → 1 based on system time */
private static float getProgress() {
boolean target = isFreeLooking;
int duration = target ? zoomInDuration.value : zoomOutDuration.value;
long now = System.currentTimeMillis();
float rawProgress = (now - animationStartTime) / (float) duration;
if (target) {
return clamp(rawProgress, 0f, 1f);
} else {
// Reverse for zoom out
return clamp(1f - rawProgress, 0f, 1f);
}
}
private static float clamp(float value, float min, float max) {
return Math.max(min, Math.min(max, value));
}
}