diff --git a/src/main/java/meteordevelopment/meteorclient/mixin/EntityMixin.java b/src/main/java/meteordevelopment/meteorclient/mixin/EntityMixin.java index 85933cd36a..f5c5b0c6b8 100644 --- a/src/main/java/meteordevelopment/meteorclient/mixin/EntityMixin.java +++ b/src/main/java/meteordevelopment/meteorclient/mixin/EntityMixin.java @@ -212,8 +212,10 @@ private void updateChangeLookDirection(double cursorDeltaX, double cursorDeltaY, FreeLook freeLook = Modules.get().get(FreeLook.class); if (freecam.isActive()) { - freecam.changeLookDirection(cursorDeltaX * 0.15, cursorDeltaY * 0.15); - ci.cancel(); + if (!freecam.getOverride()) { + freecam.changeLookDirection(cursorDeltaX * 0.15, cursorDeltaY * 0.15); + ci.cancel(); + } } else if (Modules.get().isActive(HighwayBuilder.class)) { Camera camera = mc.gameRenderer.getCamera(); diff --git a/src/main/java/meteordevelopment/meteorclient/mixin/GameRendererMixin.java b/src/main/java/meteordevelopment/meteorclient/mixin/GameRendererMixin.java index a3c6ae5d44..af6728a69d 100644 --- a/src/main/java/meteordevelopment/meteorclient/mixin/GameRendererMixin.java +++ b/src/main/java/meteordevelopment/meteorclient/mixin/GameRendererMixin.java @@ -172,47 +172,26 @@ private void updateTargetedEntityInvoke(float tickDelta, CallbackInfo info) { Freecam freecam = Modules.get().get(Freecam.class); boolean highwayBuilder = Modules.get().isActive(HighwayBuilder.class); - if ((freecam.isActive() || highwayBuilder) && client.getCameraEntity() != null && !freecamSet) { + if ((freecam.shouldChangeCrosshairTarget() || highwayBuilder) && client.getCameraEntity() != null && !freecamSet) { info.cancel(); Entity cameraE = client.getCameraEntity(); - double x = cameraE.getX(); - double y = cameraE.getY(); - double z = cameraE.getZ(); - double lastX = cameraE.lastX; - double lastY = cameraE.lastY; - double lastZ = cameraE.lastZ; - float yaw = cameraE.getYaw(); - float pitch = cameraE.getPitch(); - float lastYaw = cameraE.lastYaw; - float lastPitch = cameraE.lastPitch; - + freecamSet = true; if (highwayBuilder) { + float yaw = cameraE.getYaw(); + float pitch = cameraE.getPitch(); + cameraE.setYaw(camera.getYaw()); cameraE.setPitch(camera.getPitch()); + + updateCrosshairTarget(tickDelta); + + cameraE.setYaw(yaw); + cameraE.setPitch(pitch); } else { - ((IVec3d) cameraE.getPos()).meteor$set(freecam.pos.x, freecam.pos.y - cameraE.getEyeHeight(cameraE.getPose()), freecam.pos.z); - cameraE.lastX = freecam.prevPos.x; - cameraE.lastY = freecam.prevPos.y - cameraE.getEyeHeight(cameraE.getPose()); - cameraE.lastZ = freecam.prevPos.z; - cameraE.setYaw(freecam.yaw); - cameraE.setPitch(freecam.pitch); - cameraE.lastYaw = freecam.lastYaw; - cameraE.lastPitch = freecam.lastPitch; + Freecam.withPos(() -> updateCrosshairTarget(tickDelta)); } - - freecamSet = true; - updateCrosshairTarget(tickDelta); freecamSet = false; - - ((IVec3d) cameraE.getPos()).meteor$set(x, y, z); - cameraE.lastX = lastX; - cameraE.lastY = lastY; - cameraE.lastZ = lastZ; - cameraE.setYaw(yaw); - cameraE.setPitch(pitch); - cameraE.lastYaw = lastYaw; - cameraE.lastPitch = lastPitch; } } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AirPlace.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AirPlace.java index 15200a20d6..d293f7de53 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AirPlace.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/player/AirPlace.java @@ -12,6 +12,7 @@ import meteordevelopment.meteorclient.settings.*; import meteordevelopment.meteorclient.systems.modules.Categories; import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.systems.modules.render.Freecam; import meteordevelopment.meteorclient.utils.player.InvUtils; import meteordevelopment.meteorclient.utils.render.color.SettingColor; import meteordevelopment.meteorclient.utils.world.BlockUtils; @@ -89,7 +90,7 @@ private void onTick(TickEvent.Pre event) { if (mc.crosshairTarget != null && mc.crosshairTarget.getType() != HitResult.Type.MISS) return; double r = customRange.get() ? range.get() : mc.player.getBlockInteractionRange(); - hitResult = mc.getCameraEntity().raycast(r, 0, false); + hitResult = Freecam.withPos(() -> mc.getCameraEntity().raycast(r, 0, false)); } @EventHandler diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Freecam.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Freecam.java index e79ba5a800..487c81c38f 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Freecam.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Freecam.java @@ -6,6 +6,7 @@ package meteordevelopment.meteorclient.systems.modules.render; +import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.events.game.GameLeftEvent; import meteordevelopment.meteorclient.events.game.OpenScreenEvent; import meteordevelopment.meteorclient.events.meteor.KeyEvent; @@ -14,20 +15,21 @@ import meteordevelopment.meteorclient.events.packets.PacketEvent; import meteordevelopment.meteorclient.events.world.ChunkOcclusionEvent; import meteordevelopment.meteorclient.events.world.TickEvent; -import meteordevelopment.meteorclient.settings.BoolSetting; -import meteordevelopment.meteorclient.settings.DoubleSetting; -import meteordevelopment.meteorclient.settings.Setting; -import meteordevelopment.meteorclient.settings.SettingGroup; +import meteordevelopment.meteorclient.mixininterface.IVec3d; +import meteordevelopment.meteorclient.settings.*; import meteordevelopment.meteorclient.systems.modules.Categories; import meteordevelopment.meteorclient.systems.modules.Module; import meteordevelopment.meteorclient.systems.modules.Modules; import meteordevelopment.meteorclient.systems.modules.movement.GUIMove; import meteordevelopment.meteorclient.utils.Utils; +import meteordevelopment.meteorclient.utils.misc.Keybind; +import meteordevelopment.meteorclient.utils.misc.Producer; import meteordevelopment.meteorclient.utils.misc.input.Input; import meteordevelopment.meteorclient.utils.misc.input.KeyAction; import meteordevelopment.meteorclient.utils.player.Rotations; import meteordevelopment.orbit.EventHandler; import meteordevelopment.orbit.EventPriority; +import net.minecraft.client.option.KeyBinding; import net.minecraft.client.option.Perspective; import net.minecraft.entity.Entity; import net.minecraft.network.packet.s2c.play.DeathMessageS2CPacket; @@ -42,6 +44,7 @@ public class Freecam extends Module { private final SettingGroup sgGeneral = settings.getDefaultGroup(); + private final SettingGroup sgControl = settings.createGroup("Controls"); private final Setting speed = sgGeneral.add(new DoubleSetting.Builder() .name("speed") @@ -68,6 +71,20 @@ public class Freecam extends Module { .build() ); + private final Setting ignoreShift = sgControl.add(new BoolSetting.Builder() + .name("ignore-shift-in-GUI") + .description("Ignore left shift in GUIs. (only significant if GUI-move is on)") + .defaultValue(true) + .build() + ); + + private final Setting preserveTarget = sgGeneral.add(new BoolSetting.Builder() + .name("preserve-crosshair-target") + .description("Target the block the player is looking at instead of the block the camera is looking at") + .defaultValue(false) + .build() + ); + private final Setting toggleOnDamage = sgGeneral.add(new BoolSetting.Builder() .name("toggle-on-damage") .description("Disables freecam when you take damage.") @@ -117,8 +134,42 @@ public class Freecam extends Module { .build() ); - public final Vector3d pos = new Vector3d(); - public final Vector3d prevPos = new Vector3d(); + private final Setting relativePos = sgGeneral.add(new BoolSetting.Builder() + .name("relative") + .description("Camera moves along with the player") + .defaultValue(false) + .onChanged(this::onRelativeToggle) + .build() + ); + + private final Setting relativeBind = sgControl.add(new KeybindSetting.Builder() + .name("toggle-relative") + .description("Press this bind to toggle the relative setting (bind will not trigger other actions)") + .build() + ); + + private final Setting returnBind = sgControl.add(new KeybindSetting.Builder() + .name("return") + .description("Press this bind to return camera to player (bind will not trigger other actions)") + .build() + ); + + private final Setting overrideBind = sgControl.add(new KeybindSetting.Builder() + .name("switch-control") + .description("Press this bind to switch control between player and camera (bind will not trigger other actions)") + .build() + ); + + private final Setting overrideBindHold = sgControl.add(new BoolSetting.Builder() + .name("switch-back-on-release") + .description("Switch control back when bind is released") + .defaultValue(true) + .build() + ); + + // Will be relative to player if relativePos is set + private final Vector3d pos = new Vector3d(); + private final Vector3d prevPos = new Vector3d(); private Perspective perspective; private double speedValue; @@ -131,6 +182,8 @@ public class Freecam extends Module { private boolean forward, backward, right, left, up, down, isSneaking; + private boolean override = false; + public Freecam() { super(Categories.Render, "freecam", "Allows the camera to move away from the player."); } @@ -169,8 +222,11 @@ public void onActivate() { up = Input.isPressed(mc.options.jumpKey); down = Input.isPressed(mc.options.sneakKey); + override = false; + unpress(); if (reloadChunks.get()) mc.worldRenderer.reload(); + resetCamera(); } @Override @@ -218,7 +274,7 @@ private void onTick(TickEvent.Post event) { double velY = 0; double velZ = 0; - if (rotate.get()) { + if (rotate.get() && shouldChangeCrosshairTarget()) { BlockPos crossHairPos; Vec3d crossHairPosition; @@ -277,12 +333,47 @@ private void onTick(TickEvent.Post event) { prevPos.set(pos); pos.set(pos.x + velX, pos.y + velY, pos.z + velZ); + if (relativePos.get()) { + Vec3d delta = mc.player.getPos().subtract(mc.player.getLastRenderPos()); + prevPos.sub(delta.x, delta.y, delta.z); + } + } + + // Shadow other keybinds to not waste keyboard space when freecam is off + @EventHandler(priority = EventPriority.HIGHEST) + public void onKeyHigh(KeyEvent event) { + + if (mc.options.chatKey.matchesKey(event.key, 0)) return; + if (KeyBinding.byId("key.meteor-client.open-gui").matchesKey(event.key, 0)) return; + + if (checkGuiMove()) return; + if (handleOverrideBind()) event.cancel(); + if (override) return; + if (handleReturnBind()) event.cancel(); + if (handleRelativeBind()) event.cancel(); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onMouseButtonHigh(MouseButtonEvent event) { + + if (mc.options.chatKey.matchesMouse(event.button)) return; + if (KeyBinding.byId("key.meteor-client.open-gui").matchesMouse(event.button)) return; + + if (checkGuiMove()) return; + if (handleOverrideBind()) event.cancel(); + if (override) return; + if (handleReturnBind()) event.cancel(); + if (handleRelativeBind()) event.cancel(); } @EventHandler(priority = EventPriority.HIGH) public void onKey(KeyEvent event) { if (Input.isKeyPressed(GLFW.GLFW_KEY_F3)) return; if (checkGuiMove()) return; + if (override) return; + + // ignore shift specifically and not sneak, since that is hard-coded + if (ignoreShift.get() && mc.currentScreen != null && event.key == GLFW.GLFW_KEY_LEFT_SHIFT) return; boolean cancel = true; @@ -320,6 +411,7 @@ else if (mc.options.sneakKey.matchesKey(event.key, 0)) { @EventHandler(priority = EventPriority.HIGH) private void onMouseButton(MouseButtonEvent event) { if (checkGuiMove()) return; + if (override) return; boolean cancel = true; @@ -356,6 +448,8 @@ else if (mc.options.sneakKey.matchesMouse(event.button)) { @EventHandler(priority = EventPriority.LOW) private void onMouseScroll(MouseScrollEvent event) { + if (override) return; + if (speedScrollSensitivity.get() > 0 && mc.currentScreen == null) { speedValue += event.value * 0.25 * (speedScrollSensitivity.get() * speedValue); if (speedValue < 0.1) speedValue = 0.1; @@ -393,6 +487,17 @@ else if (event.packet instanceof HealthUpdateS2CPacket packet) { } } + private void onRelativeToggle(boolean relative) { + if (mc.cameraEntity == null) return; + if (relative) { + pos.sub(mc.cameraEntity.getX(), mc.cameraEntity.getY(), mc.cameraEntity.getZ()); + prevPos.sub(mc.cameraEntity.getX(), mc.cameraEntity.getY(), mc.cameraEntity.getZ()); + } else { + pos.add(mc.cameraEntity.getX(), mc.cameraEntity.getY(), mc.cameraEntity.getZ()); + prevPos.add(mc.cameraEntity.getX(), mc.cameraEntity.getY(), mc.cameraEntity.getZ()); + } + } + private boolean checkGuiMove() { // TODO: This is very bad but you all can cope :cope: GUIMove guiMove = Modules.get().get(GUIMove.class); @@ -400,6 +505,45 @@ private boolean checkGuiMove() { return (mc.currentScreen != null && guiMove.isActive() && guiMove.skip()); } + private void resetCamera() { + if (mc.cameraEntity == null) return; + if (relativePos.get()) pos.set(0, mc.cameraEntity.getEyeHeight(mc.cameraEntity.getPose()), 0); + else pos.set(mc.cameraEntity.getX(), mc.cameraEntity.getEyeY(), mc.cameraEntity.getZ()); + pitch = mc.cameraEntity.getPitch(); + yaw = mc.cameraEntity.getYaw(); + + prevPos.set(pos); + lastYaw = yaw; + lastPitch = pitch; + } + + private boolean overrideBindIsHeld = false; + private boolean handleOverrideBind() { + if (overrideBindIsHeld == overrideBind.get().isPressed()) return false; + overrideBindIsHeld = overrideBind.get().isPressed(); + if (overrideBindIsHeld || (overrideBindHold.get() && !overrideBindIsHeld)) { + up = down = left = right = forward = backward = false; + override ^= true; + } + return true; + } + + private boolean returnBindIsHeld = false; + private boolean handleReturnBind() { + boolean changed = returnBindIsHeld != returnBind.get().isPressed(); + returnBindIsHeld = returnBind.get().isPressed(); + if (changed && returnBindIsHeld) resetCamera(); + return changed; + } + + private boolean relativeBindIsHeld = false; + private boolean handleRelativeBind() { + boolean changed = relativeBindIsHeld != relativeBind.get().isPressed(); + relativeBindIsHeld = relativeBind.get().isPressed(); + if (changed && relativeBindIsHeld) relativePos.set(!relativePos.get()); + return changed; + } + public void changeLookDirection(double deltaX, double deltaY) { lastYaw = yaw; lastPitch = pitch; @@ -419,19 +563,94 @@ public boolean staySneaking() { } public double getX(float tickDelta) { - return MathHelper.lerp(tickDelta, prevPos.x, pos.x); + double x = MathHelper.lerp(tickDelta, prevPos.x, pos.x); + if (relativePos.get()) x += mc.cameraEntity.getX(); + return x; } public double getY(float tickDelta) { - return MathHelper.lerp(tickDelta, prevPos.y, pos.y); + double y = MathHelper.lerp(tickDelta, prevPos.y, pos.y); + if (relativePos.get()) y += mc.cameraEntity.getY(); + return y; } public double getZ(float tickDelta) { - return MathHelper.lerp(tickDelta, prevPos.z, pos.z); + double z = MathHelper.lerp(tickDelta, prevPos.z, pos.z); + if (relativePos.get()) z += mc.cameraEntity.getZ(); + return z; + } + public Vec3d getPos(float tickDelta) { + return new Vec3d(getX(tickDelta), getY(tickDelta), getZ(tickDelta)); + } + public Vec3d getPos() { + return new Vec3d(getX(1), getY(1), getZ(1)); + } + + static public R withPos(Producer c) { + Freecam f = Modules.get().get(Freecam.class); + + Entity cameraE = MeteorClient.mc.cameraEntity; + + if (!f.shouldChangeCrosshairTarget()) return c.create(); + + double x = cameraE.getX(); + double y = cameraE.getY(); + double z = cameraE.getZ(); + double lastX = cameraE.lastX; + double lastY = cameraE.lastY; + double lastZ = cameraE.lastZ; + float yaw = cameraE.getYaw(); + float pitch = cameraE.getPitch(); + float lastYaw = cameraE.lastYaw; + float lastPitch = cameraE.lastPitch; + + Vec3d lastPos = f.getPos(0); + + ((IVec3d) cameraE.getPos()).meteor$set(f.getX(1), f.getY(1) - cameraE.getEyeHeight(cameraE.getPose()), f.getZ(1)); + cameraE.lastX = lastPos.x; + cameraE.lastY = lastPos.y - cameraE.getEyeHeight(cameraE.getPose()); + cameraE.lastZ = lastPos.z; + cameraE.setYaw(f.yaw); + cameraE.setPitch(f.pitch); + cameraE.lastYaw = f.lastYaw; + cameraE.lastPitch = f.lastPitch; + + R r = c.create(); + + ((IVec3d) cameraE.getPos()).meteor$set(x, y, z); + cameraE.lastX = lastX; + cameraE.lastY = lastY; + cameraE.lastZ = lastZ; + cameraE.setYaw(yaw); + cameraE.setPitch(pitch); + cameraE.lastYaw = lastYaw; + cameraE.lastPitch = lastPitch; + + return r; + } + + static public void withPos(Runnable c) { + withPos(() -> { c.run(); return null; }); // this is silly } public double getYaw(float tickDelta) { + if (override || !mc.isWindowFocused()) return yaw; return MathHelper.lerp(tickDelta, lastYaw, yaw); } public double getPitch(float tickDelta) { + if (override || !mc.isWindowFocused()) return pitch; return MathHelper.lerp(tickDelta, lastPitch, pitch); } + + public boolean getOverride() { + return override; + } + + public boolean shouldChangeCrosshairTarget() { + return isActive() && !override && !preserveTarget.get(); + } + + enum GUIMoveMode { + Completely, + Shift, + None + } }