Skip to content

Commit 887bf44

Browse files
committed
feat: customizable aim point with body-height slider, X/Z offsets, and Cloth Config GUI
1 parent 8ee3686 commit 887bf44

8 files changed

Lines changed: 182 additions & 18 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ run/
2424
# Environment
2525
.env
2626
*.log
27+
libs/

build.gradle

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,21 @@ loom {
2121
}
2222
}
2323

24+
repositories {
25+
maven { url "https://maven.shedaniel.me/" }
26+
flatDir {
27+
dirs 'libs'
28+
}
29+
}
30+
2431
dependencies {
2532
minecraft "com.mojang:minecraft:${project.minecraft_version}"
2633
implementation "net.fabricmc:fabric-loader:${project.loader_version}"
2734
implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
35+
implementation("me.shedaniel.cloth:cloth-config-fabric:${clothconfig_version}") {
36+
exclude(group: "net.fabricmc.fabric-api")
37+
}
38+
implementation "com.terraformersmc:modmenu:${modmenu_version}"
2839
}
2940

3041
processResources {

gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ maven_group=org.codersoft.mohenjo
88
archives_base_name=aimless
99

1010
fabric_version=0.153.0+26.1.2
11+
clothconfig_version=26.1.154
12+
modmenu_version=18.0.0-beta.1

src/client/java/org/codersoft/mohenjo/aimless/client/AimlessClient.java

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ public class AimlessClient implements ClientModInitializer {
2525

2626
private static KeyMapping aimKeyBind;
2727

28-
private static final double MAX_RANGE = 3.0;
29-
private static final AimlessConfig CONFIG = AimlessConfig.load();
28+
private static final double MAX_RANGE = 4.0;
3029
private int tickCounter = 0;
3130
private boolean aiming = false;
3231

@@ -54,16 +53,24 @@ public void onInitializeClient() {
5453

5554
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) ->
5655
dispatcher.register(ClientCommands.literal("aimless")
56+
.then(ClientCommands.literal("config")
57+
.executes(ctx -> {
58+
Minecraft.getInstance().execute(() ->
59+
Minecraft.getInstance().setScreen(AimlessConfigScreen.build(null))
60+
);
61+
return 1;
62+
})
63+
)
5764
.then(ClientCommands.literal("exception")
5865
.then(ClientCommands.literal("add")
5966
.then(ClientCommands.argument("player", StringArgumentType.word())
6067
.suggests(PLAYER_SUGGESTIONS)
6168
.executes(ctx -> {
6269
String name = StringArgumentType.getString(ctx, "player");
63-
if (CONFIG.isExcepted(name)) {
70+
if (AimlessConfig.CONFIG.isExcepted(name)) {
6471
ctx.getSource().sendFeedback(Component.literal("§e" + name + " is already excepted"));
6572
} else {
66-
CONFIG.addException(name);
73+
AimlessConfig.CONFIG.addException(name);
6774
ctx.getSource().sendFeedback(Component.literal("§aAdded " + name + " to exception list"));
6875
}
6976
return 1;
@@ -75,8 +82,8 @@ public void onInitializeClient() {
7582
.suggests(PLAYER_SUGGESTIONS)
7683
.executes(ctx -> {
7784
String name = StringArgumentType.getString(ctx, "player");
78-
if (CONFIG.isExcepted(name)) {
79-
CONFIG.removeException(name);
85+
if (AimlessConfig.CONFIG.isExcepted(name)) {
86+
AimlessConfig.CONFIG.removeException(name);
8087
ctx.getSource().sendFeedback(Component.literal("§aRemoved " + name + " from exception list"));
8188
} else {
8289
ctx.getSource().sendFeedback(Component.literal("§e" + name + " is not in the exception list"));
@@ -87,7 +94,7 @@ public void onInitializeClient() {
8794
)
8895
.then(ClientCommands.literal("list")
8996
.executes(ctx -> {
90-
java.util.List<String> ex = CONFIG.getExceptions();
97+
java.util.List<String> ex = AimlessConfig.CONFIG.getExceptions();
9198
if (ex.isEmpty()) {
9299
ctx.getSource().sendFeedback(Component.literal("§eNo exceptions configured"));
93100
} else {
@@ -98,7 +105,7 @@ public void onInitializeClient() {
98105
)
99106
.then(ClientCommands.literal("clear")
100107
.executes(ctx -> {
101-
CONFIG.clearExceptions();
108+
AimlessConfig.CONFIG.clearExceptions();
102109
ctx.getSource().sendFeedback(Component.literal("§aCleared all exceptions"));
103110
return 1;
104111
})
@@ -107,13 +114,13 @@ public void onInitializeClient() {
107114
.then(ClientCommands.argument("ticks", IntegerArgumentType.integer(1, 100))
108115
.executes(ctx -> {
109116
int value = IntegerArgumentType.getInteger(ctx, "ticks");
110-
CONFIG.setReactionTicks(value);
117+
AimlessConfig.CONFIG.setReactionTicks(value);
111118
ctx.getSource().sendFeedback(Component.literal("§aReaction ticks set to " + value));
112119
return 1;
113120
})
114121
)
115122
.executes(ctx -> {
116-
ctx.getSource().sendFeedback(Component.literal("§eReaction ticks: " + CONFIG.getReactionTicks()));
123+
ctx.getSource().sendFeedback(Component.literal("§eReaction ticks: " + AimlessConfig.CONFIG.getReactionTicks()));
117124
return 1;
118125
})
119126
)
@@ -127,7 +134,10 @@ public void onInitializeClient() {
127134

128135
if (aimKeyBind.consumeClick()) {
129136
aiming = !aiming;
130-
player.sendOverlayMessage(Component.literal(aiming ? "Aimless §aEnabled §7(rt: " + CONFIG.getReactionTicks() + ")" : "Aimless §cDisabled"));
137+
player.sendOverlayMessage(Component.literal(aiming
138+
? String.format("Aimless §aEnabled §7(h: %.0f%% x: %+.1f z: %+.1f rt: %d)",
139+
AimlessConfig.CONFIG.getBodyHeight() * 100, AimlessConfig.CONFIG.getOffsetX(), AimlessConfig.CONFIG.getOffsetZ(), AimlessConfig.CONFIG.getReactionTicks())
140+
: "Aimless §cDisabled"));
131141
}
132142

133143
if (!aiming) {
@@ -136,7 +146,7 @@ public void onInitializeClient() {
136146
}
137147

138148
tickCounter++;
139-
if (tickCounter < CONFIG.getReactionTicks()) return;
149+
if (tickCounter < AimlessConfig.CONFIG.getReactionTicks()) return;
140150
tickCounter = 0;
141151

142152
Player closestTarget = findClosestPlayer(player, level);
@@ -152,7 +162,7 @@ private Player findClosestPlayer(LocalPlayer player, ClientLevel level) {
152162

153163
for (Player target : level.players()) {
154164
if (target == player || !target.isAlive() || !PlayerEntityVerifier.isLegitimateHumanPlayer(target)
155-
|| CONFIG.isExcepted(target.getGameProfile().name())) continue;
165+
|| AimlessConfig.CONFIG.isExcepted(target.getGameProfile().name())) continue;
156166

157167
double distance = player.distanceTo(target);
158168
if (distance < closestDistance) {
@@ -165,11 +175,14 @@ private Player findClosestPlayer(LocalPlayer player, ClientLevel level) {
165175

166176
private void applyAim(LocalPlayer player, Player target) {
167177
Vec3 playerEyePos = player.getEyePosition();
168-
Vec3 targetEyePos = target.getEyePosition();
169178

170-
double dx = targetEyePos.x - playerEyePos.x;
171-
double dy = targetEyePos.y - playerEyePos.y;
172-
double dz = targetEyePos.z - playerEyePos.z;
179+
double targetX = target.getX() + AimlessConfig.CONFIG.getOffsetX();
180+
double targetY = target.getY() + (AimlessConfig.CONFIG.getBodyHeight() * target.getBbHeight());
181+
double targetZ = target.getZ() + AimlessConfig.CONFIG.getOffsetZ();
182+
183+
double dx = targetX - playerEyePos.x;
184+
double dy = targetY - playerEyePos.y;
185+
double dz = targetZ - playerEyePos.z;
173186
double distanceXZ = Math.sqrt(dx * dx + dz * dz);
174187

175188
float targetYaw = (float) (Math.atan2(dz, dx) * 180.0 / Math.PI) - 90.0f;

src/client/java/org/codersoft/mohenjo/aimless/client/AimlessConfig.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ public class AimlessConfig {
1919
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
2020
private static final Path CONFIG_PATH = FabricLoader.getInstance().getConfigDir().resolve("aimless.json");
2121

22+
public static final AimlessConfig CONFIG = load();
23+
2224
private int reactionTicks = 6;
2325
private List<String> exceptions = new ArrayList<>();
26+
private double bodyHeight = 0.8;
27+
private double offsetX = 0.0;
28+
private double offsetZ = 0.0;
2429

2530
public int getReactionTicks() {
2631
return reactionTicks;
@@ -60,6 +65,33 @@ public void clearExceptions() {
6065
}
6166
}
6267

68+
public double getBodyHeight() {
69+
return bodyHeight;
70+
}
71+
72+
public void setBodyHeight(double bodyHeight) {
73+
this.bodyHeight = bodyHeight;
74+
save();
75+
}
76+
77+
public double getOffsetX() {
78+
return offsetX;
79+
}
80+
81+
public void setOffsetX(double offsetX) {
82+
this.offsetX = offsetX;
83+
save();
84+
}
85+
86+
public double getOffsetZ() {
87+
return offsetZ;
88+
}
89+
90+
public void setOffsetZ(double offsetZ) {
91+
this.offsetZ = offsetZ;
92+
save();
93+
}
94+
6395
public static AimlessConfig load() {
6496
if (Files.exists(CONFIG_PATH)) {
6597
try (Reader reader = Files.newBufferedReader(CONFIG_PATH)) {
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package org.codersoft.mohenjo.aimless.client;
2+
3+
import me.shedaniel.clothconfig2.api.ConfigBuilder;
4+
import me.shedaniel.clothconfig2.api.ConfigCategory;
5+
import me.shedaniel.clothconfig2.api.ConfigEntryBuilder;
6+
import net.minecraft.client.gui.screens.Screen;
7+
import net.minecraft.network.chat.Component;
8+
9+
public class AimlessConfigScreen {
10+
11+
public static Screen build(Screen parent) {
12+
AimlessConfig config = AimlessConfig.CONFIG;
13+
14+
ConfigBuilder builder = ConfigBuilder.create()
15+
.setParentScreen(parent)
16+
.setTitle(Component.literal("Aimless Config"));
17+
18+
ConfigEntryBuilder entryBuilder = builder.entryBuilder();
19+
20+
ConfigCategory aim = builder.getOrCreateCategory(Component.literal("Aim Point"));
21+
22+
int bodyPct = (int) Math.round(config.getBodyHeight() * 100);
23+
aim.addEntry(entryBuilder.startIntSlider(
24+
Component.literal("Body Height"),
25+
bodyPct, 0, 100)
26+
.setDefaultValue(80)
27+
.setTextGetter(v -> {
28+
double pct = v;
29+
String label;
30+
if (v >= 95) label = "Head top";
31+
else if (v >= 80) label = "Eyes";
32+
else if (v >= 60) label = "Chest";
33+
else if (v >= 40) label = "Waist";
34+
else if (v >= 20) label = "Knees";
35+
else label = "Feet";
36+
return Component.literal(String.format("%s (%.0f%%)", label, pct));
37+
})
38+
.setTooltip(Component.literal("Aim height on the target's body.\n0% = feet, 100% = top of head"))
39+
.setSaveConsumer(v -> config.setBodyHeight(v / 100.0))
40+
.build()
41+
);
42+
43+
int offX = (int) Math.round(config.getOffsetX() * 100);
44+
aim.addEntry(entryBuilder.startIntSlider(
45+
Component.literal("Horizontal Offset (X)"),
46+
offX, -400, 400)
47+
.setDefaultValue(0)
48+
.setTextGetter(v -> Component.literal(String.format("%+.2f blocks", v / 100.0)))
49+
.setTooltip(Component.literal("Left/right offset from body center.\n-4.00 to +4.00 blocks"))
50+
.setSaveConsumer(v -> config.setOffsetX(v / 100.0))
51+
.build()
52+
);
53+
54+
int offZ = (int) Math.round(config.getOffsetZ() * 100);
55+
aim.addEntry(entryBuilder.startIntSlider(
56+
Component.literal("Forward Offset (Z)"),
57+
offZ, -400, 400)
58+
.setDefaultValue(0)
59+
.setTextGetter(v -> Component.literal(String.format("%+.2f blocks", v / 100.0)))
60+
.setTooltip(Component.literal("Forward/backward offset from body center.\n-4.00 to +4.00 blocks"))
61+
.setSaveConsumer(v -> config.setOffsetZ(v / 100.0))
62+
.build()
63+
);
64+
65+
aim.addEntry(entryBuilder.startIntSlider(
66+
Component.literal("Reaction Ticks"),
67+
config.getReactionTicks(), 1, 100)
68+
.setDefaultValue(6)
69+
.setTextGetter(v -> Component.literal(v + " ticks (" + (v * 50) + "ms)"))
70+
.setTooltip(Component.literal("Delay between aim corrections.\n1 tick = 50ms, 20 ticks = 1 second"))
71+
.setSaveConsumer(config::setReactionTicks)
72+
.build()
73+
);
74+
75+
ConfigCategory targeting = builder.getOrCreateCategory(Component.literal("Targeting"));
76+
77+
String exList = config.getExceptions().isEmpty()
78+
? "None"
79+
: String.join(", ", config.getExceptions());
80+
81+
targeting.addEntry(entryBuilder.startTextDescription(
82+
Component.literal("§7Exceptions: §f" + exList + "\n\nManage via §e/aimless exception§7 commands"))
83+
.build()
84+
);
85+
86+
builder.setSavingRunnable(config::save);
87+
88+
return builder.build();
89+
}
90+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.codersoft.mohenjo.aimless.client;
2+
3+
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
4+
import com.terraformersmc.modmenu.api.ModMenuApi;
5+
6+
public class AimlessModMenu implements ModMenuApi {
7+
@Override
8+
public ConfigScreenFactory<?> getModConfigScreenFactory() {
9+
return parent -> AimlessConfigScreen.build(parent);
10+
}
11+
}

src/main/resources/fabric.mod.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
],
2323
"main": [
2424
"org.codersoft.mohenjo.aimless.Aimless"
25+
],
26+
"modmenu": [
27+
"org.codersoft.mohenjo.aimless.client.AimlessModMenu"
2528
]
2629
},
2730
"mixins": [
@@ -34,6 +37,7 @@
3437
"depends": {
3538
"fabricloader": ">=${loader_version}",
3639
"fabric-api": "*",
37-
"minecraft": "${minecraft_version}"
40+
"minecraft": "${minecraft_version}",
41+
"cloth-config": ">=16.0.0"
3842
}
3943
}

0 commit comments

Comments
 (0)