Skip to content

Commit 9ea2342

Browse files
Merge #1300 (Freecam old style raytrace option) into v7.52.1
2 parents ff013bb + 1a331f7 commit 9ea2342

File tree

6 files changed

+233
-3
lines changed

6 files changed

+233
-3
lines changed

src/gametest/java/net/wurstclient/gametest/tests/FreecamHackTest.java

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@
99

1010
import static net.wurstclient.gametest.WurstClientTestHelper.*;
1111

12+
import java.nio.file.Path;
13+
1214
import org.lwjgl.glfw.GLFW;
1315

1416
import net.fabricmc.fabric.api.client.gametest.v1.TestInput;
1517
import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
1618
import net.fabricmc.fabric.api.client.gametest.v1.context.TestClientWorldContext;
1719
import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext;
1820
import net.fabricmc.fabric.api.client.gametest.v1.context.TestSingleplayerContext;
21+
import net.minecraft.core.BlockPos;
22+
import net.minecraft.world.level.block.Blocks;
23+
import net.minecraft.world.level.block.LeverBlock;
24+
import net.minecraft.world.level.block.state.BlockState;
1925
import net.wurstclient.gametest.WurstTest;
2026

2127
public enum FreecamHackTest
@@ -129,12 +135,97 @@ public static void testFreecamHack(ClientGameTestContext context,
129135
context.waitTick();
130136
world.waitForChunksRender();
131137

132-
// Clean up
138+
// Reset player and remove walkway
133139
runCommand(server, "fill 0 -58 1 0 -58 2 air");
134140
runCommand(server, "tp @s 0 -57 0 0 0");
135-
context.waitTicks(2);
136-
world.waitForChunksRender();
137141
// Restore body rotation - /tp only rotates the head as of 1.21.11
138142
context.runOnClient(mc -> mc.player.setYBodyRot(0));
143+
144+
// Test "Interact from" setting
145+
runCommand(server, "setblock 0 -56 2 smooth_stone");
146+
waitForBlock(context, 0, 1, 2, Blocks.SMOOTH_STONE);
147+
runCommand(server, "setblock 0 -56 1 lever[face=wall,facing=north]");
148+
runCommand(server, "setblock 0 -56 3 lever[face=wall,facing=south]");
149+
waitForBlock(context, 0, 1, 3, Blocks.LEVER);
150+
context.waitTicks(WurstTest.IS_MOD_COMPAT_TEST ? 5 : 1);
151+
world.waitForChunksRender();
152+
context.takeScreenshot("freecam_interact_setup");
153+
154+
// Enable Freecam and fly to a side view
155+
runWurstCommand(context, "setslider Freecam horizontal_speed 0.95");
156+
input.pressKey(GLFW.GLFW_KEY_U);
157+
input.holdKeyFor(GLFW.GLFW_KEY_W, 3);
158+
context.waitTick();
159+
runWurstCommand(context, "setslider Freecam horizontal_speed 1");
160+
for(int i = 0; i < 6; i++)
161+
{
162+
input.moveCursor(120, 0);
163+
context.waitTick();
164+
}
165+
input.holdKeyFor(GLFW.GLFW_KEY_S, 2);
166+
context.waitTick();
167+
world.waitForChunksRender();
168+
context.takeScreenshot("freecam_interact_side_view");
169+
170+
// Right click with "Interact from: Player" (default)
171+
input.pressMouse(GLFW.GLFW_MOUSE_BUTTON_RIGHT);
172+
context.waitTick();
173+
assertLeverState(context, spContext, 0, -56, 1, true,
174+
"near lever, player mode");
175+
assertLeverState(context, spContext, 0, -56, 3, false,
176+
"far lever, player mode");
177+
178+
// Switch to "Interact from: Camera" and right click
179+
runWurstCommand(context, "setmode Freecam interact_from camera");
180+
input.pressMouse(GLFW.GLFW_MOUSE_BUTTON_RIGHT);
181+
context.waitTick();
182+
assertLeverState(context, spContext, 0, -56, 3, true,
183+
"far lever, camera mode");
184+
assertLeverState(context, spContext, 0, -56, 1, true,
185+
"near lever, camera mode");
186+
187+
// Clean up
188+
runCommand(server, "fill 0 -56 1 0 -56 3 air");
189+
runWurstCommand(context, "setmode Freecam interact_from player");
190+
input.pressKey(GLFW.GLFW_KEY_U);
191+
context.waitTick();
192+
world.waitForChunksRender();
193+
}
194+
195+
private static void assertLeverState(ClientGameTestContext context,
196+
TestSingleplayerContext spContext, int x, int y, int z,
197+
boolean expectedPowered, String description)
198+
{
199+
TestServerContext server = spContext.getServer();
200+
BlockState state = server.computeOnServer(
201+
s -> s.overworld().getBlockState(new BlockPos(x, y, z)));
202+
203+
String errorMessage = null;
204+
if(state.getBlock() != Blocks.LEVER)
205+
errorMessage = "Expected lever at " + x + ", " + y + ", " + z + " ("
206+
+ description + ") but found " + state;
207+
else if(state.getValue(LeverBlock.POWERED) != expectedPowered)
208+
errorMessage = "Lever at " + x + ", " + y + ", " + z + " ("
209+
+ description + ") expected powered=" + expectedPowered
210+
+ " but was powered=" + !expectedPowered;
211+
212+
if(errorMessage == null)
213+
return;
214+
215+
TestClientWorldContext world = spContext.getClientWorld();
216+
context.waitTick();
217+
world.waitForChunksRender();
218+
219+
String fileName = "freecam_interact_failed";
220+
Path screenshotPath = context.takeScreenshot(fileName);
221+
ghSummary("### Freecam interact test failed");
222+
ghSummary(errorMessage);
223+
String url = tryUploadToImgur(screenshotPath);
224+
if(url != null)
225+
ghSummary("![" + fileName + "](" + url + ")");
226+
else
227+
ghSummary("Couldn't upload " + fileName
228+
+ ".png to Imgur. Check the Test Screenshots.zip artifact.");
229+
throw new RuntimeException(errorMessage);
139230
}
140231
}

src/main/java/net/wurstclient/hacks/FreecamHack.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import net.wurstclient.hacks.freecam.FreecamInitialPosSetting;
2929
import net.wurstclient.hacks.freecam.FreecamInputSetting;
3030
import net.wurstclient.hacks.freecam.FreecamInputSetting.ApplyInputTo;
31+
import net.wurstclient.hacks.freecam.FreecamInteractionSetting;
32+
import net.wurstclient.hacks.freecam.FreecamInteractionSetting.InteractFrom;
3133
import net.wurstclient.mixinterface.IKeyMapping;
3234
import net.wurstclient.settings.CheckboxSetting;
3335
import net.wurstclient.settings.ColorSetting;
@@ -45,6 +47,9 @@ public final class FreecamHack extends Hack
4547
{
4648
private final FreecamInputSetting applyInputTo = new FreecamInputSetting();
4749

50+
private final FreecamInteractionSetting interactFrom =
51+
new FreecamInteractionSetting();
52+
4853
private final SliderSetting horizontalSpeed =
4954
new SliderSetting("Horizontal speed",
5055
"description.wurst.setting.freecam.horizontal_speed", 1, 0.05, 10,
@@ -90,6 +95,7 @@ public FreecamHack()
9095
super("Freecam");
9196
setCategory(Category.RENDER);
9297
addSetting(applyInputTo);
98+
addSetting(interactFrom);
9399
addSetting(horizontalSpeed);
94100
addSetting(verticalSpeed);
95101
addSetting(scrollToChangeSpeed);
@@ -215,6 +221,11 @@ public boolean isMovingCamera()
215221
return isEnabled() && applyInputTo.getSelected() == ApplyInputTo.CAMERA;
216222
}
217223

224+
public boolean isClickingFromCamera()
225+
{
226+
return isEnabled() && interactFrom.getSelected() == InteractFrom.CAMERA;
227+
}
228+
218229
@Override
219230
public void onVisGraph(VisGraphEvent event)
220231
{
@@ -274,4 +285,9 @@ public float getCamPitch()
274285
{
275286
return camPitch;
276287
}
288+
289+
public Vec3 getScaledCamDir(double scale)
290+
{
291+
return Vec3.directionFromRotation(camPitch, camYaw).scale(scale);
292+
}
277293
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (c) 2014-2026 Wurst-Imperium and contributors.
3+
*
4+
* This source code is subject to the terms of the GNU General Public
5+
* License, version 3. If a copy of the GPL was not distributed with this
6+
* file, You can obtain one at: https://www.gnu.org/licenses/gpl-3.0.txt
7+
*/
8+
package net.wurstclient.hacks.freecam;
9+
10+
import net.wurstclient.settings.EnumSetting;
11+
import net.wurstclient.util.text.WText;
12+
13+
public final class FreecamInteractionSetting
14+
extends EnumSetting<FreecamInteractionSetting.InteractFrom>
15+
{
16+
private static final WText DESCRIPTION = buildDescription();
17+
18+
public FreecamInteractionSetting()
19+
{
20+
super("Interact from", DESCRIPTION, InteractFrom.values(),
21+
InteractFrom.PLAYER);
22+
}
23+
24+
private static WText buildDescription()
25+
{
26+
WText text =
27+
WText.translated("description.wurst.setting.freecam.interact_from");
28+
29+
for(InteractFrom value : InteractFrom.values())
30+
text = text
31+
.append(WText.literal("\n\n\u00a7l" + value.name + ":\u00a7r "))
32+
.append(value.description);
33+
34+
return text;
35+
}
36+
37+
public enum InteractFrom
38+
{
39+
CAMERA("Camera"),
40+
PLAYER("Player");
41+
42+
private static final String TRANSLATION_KEY_PREFIX =
43+
"description.wurst.setting.freecam.interact_from.";
44+
45+
private final String name;
46+
private final WText description;
47+
48+
private InteractFrom(String name)
49+
{
50+
this.name = name;
51+
description =
52+
WText.translated(TRANSLATION_KEY_PREFIX + name().toLowerCase());
53+
}
54+
55+
@Override
56+
public String toString()
57+
{
58+
return name;
59+
}
60+
}
61+
}

src/main/java/net/wurstclient/mixin/freecam/LocalPlayerMixin.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*/
88
package net.wurstclient.mixin.freecam;
99

10+
import java.util.function.Predicate;
11+
1012
import org.spongepowered.asm.mixin.Mixin;
1113
import org.spongepowered.asm.mixin.Shadow;
1214
import org.spongepowered.asm.mixin.Unique;
@@ -15,12 +17,22 @@
1517
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
1618
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
1719

20+
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
21+
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
22+
import com.llamalad7.mixinextras.sugar.Local;
1823
import com.mojang.authlib.GameProfile;
1924

2025
import net.minecraft.client.multiplayer.ClientLevel;
2126
import net.minecraft.client.player.AbstractClientPlayer;
2227
import net.minecraft.client.player.ClientInput;
2328
import net.minecraft.client.player.LocalPlayer;
29+
import net.minecraft.world.entity.Entity;
30+
import net.minecraft.world.entity.EntityType;
31+
import net.minecraft.world.level.ClipContext;
32+
import net.minecraft.world.phys.AABB;
33+
import net.minecraft.world.phys.EntityHitResult;
34+
import net.minecraft.world.phys.HitResult;
35+
import net.minecraft.world.phys.Vec3;
2436
import net.wurstclient.WurstClient;
2537
import net.wurstclient.hacks.FreecamHack;
2638

@@ -79,4 +91,48 @@ public void turn(double deltaYaw, double deltaPitch)
7991

8092
super.turn(deltaYaw, deltaPitch);
8193
}
94+
95+
@WrapOperation(
96+
method = "pick(Lnet/minecraft/world/entity/Entity;DDF)Lnet/minecraft/world/phys/HitResult;",
97+
at = @At(value = "INVOKE",
98+
target = "Lnet/minecraft/world/entity/Entity;pick(DFZ)Lnet/minecraft/world/phys/HitResult;"))
99+
private static HitResult modifyBlockRaycast(Entity player, double maxDist,
100+
float partialTicks, boolean includeFluids,
101+
Operation<HitResult> original)
102+
{
103+
FreecamHack freecam = WurstClient.INSTANCE.getHax().freecamHack;
104+
if(!freecam.isClickingFromCamera())
105+
return original.call(player, maxDist, partialTicks, includeFluids);
106+
107+
Vec3 camStart = freecam.getCamPos(partialTicks);
108+
Vec3 camEnd = camStart.add(freecam.getScaledCamDir(maxDist));
109+
return player.level()
110+
.clip(new ClipContext(camStart, camEnd, ClipContext.Block.OUTLINE,
111+
includeFluids ? ClipContext.Fluid.ANY : ClipContext.Fluid.NONE,
112+
player));
113+
}
114+
115+
@WrapOperation(
116+
method = "pick(Lnet/minecraft/world/entity/Entity;DDF)Lnet/minecraft/world/phys/HitResult;",
117+
at = @At(value = "INVOKE",
118+
target = "Lnet/minecraft/world/entity/projectile/ProjectileUtil;getEntityHitResult(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;D)Lnet/minecraft/world/phys/EntityHitResult;"))
119+
private static EntityHitResult modifyEntityRaycast(Entity instance,
120+
Vec3 start, Vec3 end, AABB bounds, Predicate<Entity> predicate,
121+
double maxDistSq, Operation<EntityHitResult> original,
122+
@Local(ordinal = 0) double maxDist)
123+
{
124+
FreecamHack freecam = WurstClient.INSTANCE.getHax().freecamHack;
125+
if(!freecam.isClickingFromCamera())
126+
return original.call(instance, start, end, bounds, predicate,
127+
maxDistSq);
128+
129+
Vec3 camStart = freecam.getCamPos(1F);
130+
Vec3 scaledCamDir = freecam.getScaledCamDir(maxDist);
131+
Vec3 camEnd = camStart.add(scaledCamDir);
132+
AABB camBounds = EntityType.PLAYER.getDimensions()
133+
.makeBoundingBox(camStart).expandTowards(scaledCamDir).inflate(1);
134+
135+
return original.call(instance, camStart, camEnd, camBounds, predicate,
136+
maxDistSq);
137+
}
82138
}

src/main/resources/assets/wurst/translations/de_de.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@
9898
"description.wurst.setting.freecam.apply_input_to": "Was deine Tastatur- und Mauseingaben tun sollen, während Freecam aktiv ist.",
9999
"description.wurst.setting.freecam.apply_input_to.camera": "Bewegt die Kamera, während deine Spielfigur stillsteht.",
100100
"description.wurst.setting.freecam.apply_input_to.player": "Bewegt deine Spielfigur, während die Kamera stillsteht.",
101+
"description.wurst.setting.freecam.interact_from": "Was deine Links-/Rechts-/Mittelklicks treffen sollen, während Freecam aktiv ist.",
102+
"description.wurst.setting.freecam.interact_from.camera": "Klicks treffen das, worauf die Kamera schaut. Das ermöglicht dir, mit Blöcken und Mobs hinter Wänden zu interagieren, solange sie nicht zu weit von deiner Spielfigur entfernt sind.",
103+
"description.wurst.setting.freecam.interact_from.player": "Klicks treffen das, worauf deine Spielfigur schaut.",
101104
"description.wurst.setting.freecam.horizontal_speed": "Horizontale Geschwindigkeit von Kamerabewegungen während du Freecam benutzt.\n\nHat keine Wirkung, wenn §lApply §linput §lto§r auf §lPlayer§r gesetzt ist.",
102105
"description.wurst.setting.freecam.vertical_speed": "Vertikale Geschwindigkeit von Kamerabewegungen während du Freecam benutzt (relativ zur horizontalen).\n\nHat keine Wirkung, wenn §lApply §linput §lto§r auf §lPlayer§r gesetzt ist.",
103106
"description.wurst.setting.freecam.scroll_to_change_speed": "Ändert deine Kamerageschwindigkeit, wenn du das Mausrad scrollst.\n\nHat keine Wirkung, wenn §lApply §linput §lto§r auf §lPlayer§r gesetzt ist.",

src/main/resources/assets/wurst/translations/en_us.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@
148148
"description.wurst.setting.freecam.apply_input_to": "What your keyboard and mouse inputs should do while Freecam is active.",
149149
"description.wurst.setting.freecam.apply_input_to.camera": "Moves the camera around while your character stays still.",
150150
"description.wurst.setting.freecam.apply_input_to.player": "Moves your character while the camera stays still.",
151+
"description.wurst.setting.freecam.interact_from": "What your left/right/middle clicks should target while Freecam is active.",
152+
"description.wurst.setting.freecam.interact_from.camera": "Clicks target what the camera is looking at. This allows you to interact with blocks and entities behind walls as long as they're not too far away from your character.",
153+
"description.wurst.setting.freecam.interact_from.player": "Clicks target what your character is looking at.",
151154
"description.wurst.setting.freecam.horizontal_speed": "Horizontal camera movement speed when using Freecam.\n\nDoes nothing when §lApply §linput §lto§r is set to §lPlayer§r.",
152155
"description.wurst.setting.freecam.vertical_speed": "Vertical camera movement speed when using Freecam (relative to horizontal).\n\nDoes nothing when §lApply §linput §lto§r is set to §lPlayer§r.",
153156
"description.wurst.setting.freecam.scroll_to_change_speed": "Changes your camera movement speed when you scroll the mouse wheel.\n\nDoes nothing when §lApply §linput §lto§r is set to §lPlayer§r.",

0 commit comments

Comments
 (0)