diff --git a/gradle.properties b/gradle.properties index ede98d93..b7124134 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,6 @@ -org.gradle.jvmargs=-Xmx2G +org.gradle.jvmargs=-Xmx2G --enable-native-access=ALL-UNNAMED org.gradle.parallel=true +org.gradle.caching=true # Fabric Properties minecraft_version=1.21.5 diff --git a/src/main/java/dev/cigarette/agent/MurderMysteryAgent.java b/src/main/java/dev/cigarette/agent/MurderMysteryAgent.java index 5cbe148f..fbbae026 100644 --- a/src/main/java/dev/cigarette/agent/MurderMysteryAgent.java +++ b/src/main/java/dev/cigarette/agent/MurderMysteryAgent.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.HashSet; +import java.util.Optional; import java.util.UUID; public class MurderMysteryAgent extends BaseAgent { @@ -53,10 +54,9 @@ private void cleanupAvailableGold() { } } - private boolean isDetectiveItem(ItemStack item) { + public static boolean isDetectiveItem(ItemStack item) { if (item.isOf(Items.ARROW)) return true; - if (item.isOf(Items.BOW)) return true; - return false; + return item.isOf(Items.BOW); } private PersistentPlayer getOrCreatePersistentPlayer(PlayerEntity player) { @@ -83,6 +83,13 @@ private void createGold(ItemEntity item) { availableGold.add(gold); } + public static PersistentPlayer.Role getRole(PlayerEntity player) { + String playerName = player.getNameForScoreboard(); + PersistentPlayer persistPlayer = persistentPlayers.get(playerName); + if (persistPlayer == null) return PersistentPlayer.Role.INNOCENT; + return persistPlayer.role; + } + @Override public boolean inValidGame() { @@ -104,6 +111,7 @@ protected void onValidTick(MinecraftClient client, @NotNull ClientWorld world, @ if (item == ItemStack.EMPTY) continue; if (this.isDetectiveItem(item)) { existingPlayer.setDetective(); + existingPlayer.setItemStack(item); continue; } @@ -113,6 +121,7 @@ protected void onValidTick(MinecraftClient client, @NotNull ClientWorld world, @ for (String knife : knives) { if (knife.equals(knifeLang)) { existingPlayer.setMurderer(); + existingPlayer.setItemStack(item); break; } } @@ -139,6 +148,7 @@ public static class PersistentPlayer { public PlayerEntity playerEntity; public final String name; public Role role; + public ItemStack itemStack; public PersistentPlayer(PlayerEntity playerEntity) { this.playerEntity = playerEntity; @@ -146,6 +156,10 @@ public PersistentPlayer(PlayerEntity playerEntity) { this.role = Role.INNOCENT; } + public void setItemStack(ItemStack itemStack) { + this.itemStack = itemStack; + } + protected void setPlayerEntity(PlayerEntity playerEntity) { this.playerEntity = playerEntity; } diff --git a/src/main/java/dev/cigarette/agent/ZombiesAgent.java b/src/main/java/dev/cigarette/agent/ZombiesAgent.java index 732f66f9..44791bdb 100644 --- a/src/main/java/dev/cigarette/agent/ZombiesAgent.java +++ b/src/main/java/dev/cigarette/agent/ZombiesAgent.java @@ -92,7 +92,7 @@ public static ZombiesAgent.ZombieTarget getBestTarget(ClientPlayerEntity player) return bestTarget; } - private Vec3d calculatePredictedPosition(ZombiesAgent.ZombieTarget zombie, ClientPlayerEntity player) { + public static Vec3d calculatePredictedPosition(ZombiesAgent.ZombieTarget zombie, ClientPlayerEntity player) { if (!Aimbot.INSTANCE.predictiveAim.getRawState()) { return zombie.entity.getPos().subtract(player.getPos().add(0, player.getEyeHeight(EntityPose.STANDING), 0)); } @@ -119,7 +119,36 @@ private Vec3d calculatePredictedPosition(ZombiesAgent.ZombieTarget zombie, Clien return predictedBodyPos.add(0, zombie.entity.getEyeHeight(zombie.entity.getPose()), 0); } - private Vec3d getPathfindingDirection(ZombiesAgent.ZombieTarget zombie, Vec3d zombiePos, Vec3d playerPos, Vec3d currentVelocity) { + public static Vec3d calculatePredictedPosition(ClientPlayerEntity target, ClientPlayerEntity player) { + Vec3d currentPos = target.getPos(); + Vec3d playerPos = player.getEyePos(); + Vec3d instantVelocity = target.getPos().subtract(target.lastX, target.lastY, target.lastZ); + + double xVelocity = instantVelocity.x * Aimbot.INSTANCE.predictionTicks.getRawState().intValue(); + double yVelocity = instantVelocity.y > LivingEntity.GRAVITY ? 0 : instantVelocity.y * (instantVelocity.y > 0 ? 1 : Aimbot.INSTANCE.predictionTicks.getRawState().intValue()); + double zVelocity = instantVelocity.z * Aimbot.INSTANCE.predictionTicks.getRawState().intValue(); + Vec3d currentVelocity = new Vec3d(xVelocity, yVelocity, zVelocity); + + Vec3d pathDirection = getPathfindingDirection(null, currentPos, playerPos, currentVelocity); + + if (pathDirection.lengthSquared() < 1.0E-7D) { + return target.getEyePos(); + } + + double distance = playerPos.distanceTo(currentPos); + double projectileSpeed = 20.0; + double timeToTarget = distance / projectileSpeed; + + int maxPredictionTicks = Aimbot.INSTANCE.predictionTicks.getRawState().intValue(); + double maxPredictionTime = maxPredictionTicks / 20.0; + timeToTarget = Math.min(timeToTarget, maxPredictionTime); + + Vec3d predictedBodyPos = currentPos.add(pathDirection.multiply(timeToTarget)); + + return predictedBodyPos.add(0, target.getEyeHeight(target.getPose()), 0); + } + + private static Vec3d getPathfindingDirection(ZombiesAgent.ZombieTarget zombie, Vec3d zombiePos, Vec3d playerPos, Vec3d currentVelocity) { Vec3d directPath = playerPos.subtract(zombiePos).normalize(); if (currentVelocity.lengthSquared() > 1.0E-7D) { @@ -133,7 +162,7 @@ private Vec3d getPathfindingDirection(ZombiesAgent.ZombieTarget zombie, Vec3d zo return directPath.multiply(estimateZombieSpeed(zombie) / 20.0); } - private double estimateZombieSpeed(ZombiesAgent.ZombieTarget zombie) { + private static double estimateZombieSpeed(ZombiesAgent.ZombieTarget zombie) { return switch (zombie.type) { case ZOMBIE, SKELETON, CREEPER, WITCH -> 5.0; // ~0.25 B/t * 20 t/s case BLAZE -> 8.0; diff --git a/src/main/java/dev/cigarette/config/Config.java b/src/main/java/dev/cigarette/config/Config.java index 7af5c2ef..1a05967b 100644 --- a/src/main/java/dev/cigarette/config/Config.java +++ b/src/main/java/dev/cigarette/config/Config.java @@ -8,9 +8,11 @@ import dev.cigarette.module.combat.AutoClicker; import dev.cigarette.module.combat.JumpReset; import dev.cigarette.module.combat.PerfectHit; +import dev.cigarette.module.combat.PlayerAimbot; import dev.cigarette.module.keybind.AddGlassBlock; import dev.cigarette.module.keybind.BreakBlock; import dev.cigarette.module.keybind.VClip; +import dev.cigarette.module.murdermystery.AutoBow; import dev.cigarette.module.murdermystery.GoldESP; import dev.cigarette.module.murdermystery.PlayerESP; import dev.cigarette.module.render.ProjectileESP; @@ -68,9 +70,9 @@ public void positionCategories() { public static Config construct() { Config cfg = new Config(); cfg.putModules("Bedwars", AutoBlockIn.INSTANCE, AutoTool.INSTANCE, Bridger.INSTANCE, DefenseViewer.INSTANCE, EntityESP.INSTANCE, FireballESP.INSTANCE); - cfg.putModules("Combat", AutoClicker.INSTANCE, JumpReset.INSTANCE, PerfectHit.INSTANCE); + cfg.putModules("Combat", AutoClicker.INSTANCE, JumpReset.INSTANCE, PerfectHit.INSTANCE, PlayerAimbot.INSTANCE); cfg.putModules("Keybind", AddGlassBlock.INSTANCE, BreakBlock.INSTANCE, VClip.INSTANCE); - cfg.putModules("Murder Mystery", GoldESP.INSTANCE, PlayerESP.INSTANCE); + cfg.putModules("Murder Mystery", GoldESP.INSTANCE, PlayerESP.INSTANCE, AutoBow.INSTANCE); cfg.putModules("Render", dev.cigarette.module.render.PlayerESP.INSTANCE, ProjectileESP.INSTANCE); cfg.putModules("UI", GUI.INSTANCE, ModuleList.INSTANCE, Notifications.INSTANCE, TargetHUD.INSTANCE, Watermark.INSTANCE); cfg.putModules("Zombies", Aimbot.INSTANCE, PowerupESP.INSTANCE, ReviveAura.INSTANCE, ZombieESP.INSTANCE); diff --git a/src/main/java/dev/cigarette/gui/hud/bar/BarDisplay.java b/src/main/java/dev/cigarette/gui/hud/bar/BarDisplay.java index e28fc053..8ba792d7 100644 --- a/src/main/java/dev/cigarette/gui/hud/bar/BarDisplay.java +++ b/src/main/java/dev/cigarette/gui/hud/bar/BarDisplay.java @@ -71,6 +71,7 @@ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float d List widgets = new ArrayList<>(); Set seenUuids = new HashSet<>(); List> providerOrder = List.of( + dev.cigarette.gui.hud.bar.providers.AimbotTargetProvider.class, dev.cigarette.gui.hud.bar.providers.MurderMysteryProvider.class, dev.cigarette.gui.hud.bar.providers.BedwarsProvider.class, dev.cigarette.gui.hud.bar.providers.ZombiesProvider.class, diff --git a/src/main/java/dev/cigarette/gui/hud/bar/providers/AimbotTargetProvider.java b/src/main/java/dev/cigarette/gui/hud/bar/providers/AimbotTargetProvider.java new file mode 100644 index 00000000..d2c8ee61 --- /dev/null +++ b/src/main/java/dev/cigarette/gui/hud/bar/providers/AimbotTargetProvider.java @@ -0,0 +1,73 @@ +package dev.cigarette.gui.hud.bar.providers; + +import dev.cigarette.gui.CigaretteScreen; +import dev.cigarette.gui.hud.bar.BarDisplay; +import dev.cigarette.gui.hud.bar.api.BarWidget; +import dev.cigarette.gui.hud.bar.api.BarWidgetProvider; +import dev.cigarette.gui.hud.bar.widgets.EntityChipWidget; +import dev.cigarette.module.combat.PlayerAimbot; +import dev.cigarette.module.zombies.Aimbot; +import dev.cigarette.agent.ZombiesAgent; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; + +import java.util.List; +import java.util.UUID; + +public class AimbotTargetProvider implements BarWidgetProvider { + @Override + public void collect(MinecraftClient mc, ClientWorld world, TextRenderer tr, List out) { + if (mc == null || world == null || mc.player == null) return; + + boolean playerAimbotRunning = false; + boolean zombiesAimbotRunning = false; + try { playerAimbotRunning = PlayerAimbot.INSTANCE.isRunning(); } catch (Throwable ignored) {} + try { zombiesAimbotRunning = Aimbot.INSTANCE.isRunning(); } catch (Throwable ignored) {} + + if (playerAimbotRunning) { + AbstractClientPlayerEntity target = PlayerAimbot.getBestTargetFor(mc.player); + if (target != null) { + String label = target.getName().getString(); + double sortKey = 0d; + try { + double dx = target.getX() - mc.player.getX(); + double dz = target.getZ() - mc.player.getZ(); + sortKey = BarDisplay.angle(mc.player.getYaw(), dx, dz); + label += " (" + Math.round(mc.player.distanceTo(target)) + "m)"; + } catch (Throwable ignored) {} + float hp = Math.max(0f, Math.min(1f, target.getHealth() / target.getMaxHealth())); + out.add(new EntityChipWidget.Progress("aimbot:player:" + target.getUuid(), target, label, sortKey, 0, + hp, CigaretteScreen.SECONDARY_COLOR, CigaretteScreen.BACKGROUND_COLOR)); + } + } + + if (zombiesAimbotRunning) { + ZombiesAgent.ZombieTarget zt = ZombiesAgent.getClosestZombie(); + if (zt != null && zt.uuid != null) { + Entity ent = findEntityByUuid(world, zt.uuid); + String label = zt.type.getName(); + double sortKey = 0d; + if (ent == null) { + label = label + " [?]"; + sortKey = 180.0; + } else { + try { + double dx = ent.getX() - mc.player.getX(); + double dz = ent.getZ() - mc.player.getZ(); + sortKey = BarDisplay.angle(mc.player.getYaw(), dx, dz); + label += " (" + Math.round(mc.player.distanceTo(ent)) + "m)"; + } catch (Throwable ignored) {} + } + out.add(new EntityChipWidget("aimbot:zombies:" + zt.uuid, ent, label, sortKey, zt.type.getColor())); + } + } + } + + private static Entity findEntityByUuid(ClientWorld world, UUID uuid) { + for (Entity e : world.getEntities()) if (uuid.equals(e.getUuid())) return e; + return null; + } +} diff --git a/src/main/java/dev/cigarette/gui/hud/bar/providers/DefaultProviders.java b/src/main/java/dev/cigarette/gui/hud/bar/providers/DefaultProviders.java index 34006bca..00f964b1 100644 --- a/src/main/java/dev/cigarette/gui/hud/bar/providers/DefaultProviders.java +++ b/src/main/java/dev/cigarette/gui/hud/bar/providers/DefaultProviders.java @@ -10,6 +10,6 @@ public static void registerAll() { BarWidgetRegistry.register(new ZombiesProvider()); BarWidgetRegistry.register(new BedwarsProvider()); BarWidgetRegistry.register(new NearbyPlayersProvider()); + BarWidgetRegistry.register(new AimbotTargetProvider()); } } - diff --git a/src/main/java/dev/cigarette/gui/widget/SliderWidget.java b/src/main/java/dev/cigarette/gui/widget/SliderWidget.java index a0afa7e2..2de61695 100644 --- a/src/main/java/dev/cigarette/gui/widget/SliderWidget.java +++ b/src/main/java/dev/cigarette/gui/widget/SliderWidget.java @@ -13,9 +13,9 @@ public class SliderWidget extends BaseWidget { private static final int SLIDER_PADDING = 4; private @Nullable Consumer sliderCallback = null; - private double maxState = 0; - private double minState = 0; - private int decimalPlaces = 0; + double maxState = 0; + double minState = 0; + int decimalPlaces = 0; private boolean dragging = false; public boolean disabled = false; @@ -121,4 +121,142 @@ protected void render(DrawContext context, boolean hovered, int mouseX, int mous context.drawVerticalLine(sliderXState, bottom - 7, bottom - 1, primaryColor); context.drawVerticalLine(sliderXState + 1, bottom - 6, bottom - 2, primaryColor); } + + + public class TwoHandedSliderWidget extends SliderWidget { + private final SliderWidget secondSlider; + + public TwoHandedSliderWidget(int x, int y, int width, int height, String message, @Nullable String tooltip) { + super(x, y, width, height, message, tooltip); + secondSlider = new SliderWidget(x + width + 5, y, width, height, message + " 2", tooltip); + secondSlider.withBounds(this.minState, this.maxState, this.maxState); + secondSlider.withAccuracy(this.decimalPlaces); + secondSlider.sliderCallback = (value) -> { + if (value < this.getRawState()) { + this.setState(value); + } + }; + } + + public TwoHandedSliderWidget(int x, int y, int width, int height, String message) { + super(x, y, width, height, message); + secondSlider = new SliderWidget(x + width + 5, y, width, height, message + " 2"); + secondSlider.withBounds(this.minState, this.maxState, this.maxState); + secondSlider.withAccuracy(this.decimalPlaces); + secondSlider.sliderCallback = (value) -> { + if (value < this.getRawState()) { + this.setState(value); + } + }; + } + + public TwoHandedSliderWidget(String message, String tooltip) { + super(message, tooltip); + secondSlider = new SliderWidget(message + " 2", tooltip); + secondSlider.withBounds(this.minState, this.maxState, this.maxState); + secondSlider.withAccuracy(this.decimalPlaces); + secondSlider.sliderCallback = (value) -> { + if (value < this.getRawState()) { + this.setState(value); + } + }; + } + + public TwoHandedSliderWidget(String message) { + super(message); + secondSlider = new SliderWidget(message + " 2"); + secondSlider.withBounds(this.minState, this.maxState, this.maxState); + secondSlider.withAccuracy(this.decimalPlaces); + secondSlider.sliderCallback = (value) -> { + if (value < this.getRawState()) { + this.setState(value); + } + }; + } + + @Override + public void setState(double state) { + super.setState(state); + if (secondSlider.getRawState() < state) { + secondSlider.setState(state); + } + } + + @Override + protected void render(DrawContext context, boolean hovered, int mouseX, int mouseY, float deltaTicks, int left, int top, int right, int bottom) { + super.render(context, hovered, mouseX, mouseY, deltaTicks, left, top, right, bottom); + secondSlider.withXY(left + this.getWidth() + 5, top); + secondSlider.render(context, secondSlider.isMouseOver(mouseX, mouseY), mouseX, mouseY, deltaTicks, secondSlider.getX(), secondSlider.getY(), secondSlider.getX() + secondSlider.getWidth(), secondSlider.getY() + secondSlider.getHeight()); + } + } + + public static class TwoHandedSlider extends SliderWidget { + private final SliderWidget maxSlider; + + public TwoHandedSlider(String message, String tooltip) { + super(message, tooltip); + maxSlider = new SliderWidget(message + " Max", tooltip); + syncBounds(); + linkCallbacks(); + } + + public TwoHandedSlider(String message) { + super(message); + maxSlider = new SliderWidget(message + " Max"); + syncBounds(); + linkCallbacks(); + } + + private void syncBounds() { + maxSlider.withBounds(this.minState, this.maxState, this.maxState); + maxSlider.withAccuracy(this.decimalPlaces); + } + + private void linkCallbacks() { + maxSlider.sliderCallback = (value) -> { + if (value < this.getRawState()) { + this.setState(value); + } + }; + } + + @Override + public TwoHandedSlider withBounds(double min, double def, double max) { + super.withBounds(min, def, max); + syncBounds(); + if (maxSlider.getRawState() < getRawState()) maxSlider.setState(getRawState()); + return this; + } + + @Override + public TwoHandedSlider withAccuracy(int decimalPlaces) { + super.withAccuracy(decimalPlaces); + maxSlider.withAccuracy(decimalPlaces); + return this; + } + + @Override + public void setState(double state) { + super.setState(state); + if (maxSlider.getRawState() < state) { + maxSlider.setState(state); + } + } + + @Override + public void registerConfigKey(String key) { + super.registerConfigKey(key + ".min"); + maxSlider.registerConfigKey(key + ".max"); + } + + public double getMinValue() { return this.getRawState(); } + public double getMaxValue() { return maxSlider.getRawState(); } + + @Override + protected void render(DrawContext context, boolean hovered, int mouseX, int mouseY, float deltaTicks, int left, int top, int right, int bottom) { + super.render(context, hovered, mouseX, mouseY, deltaTicks, left, top, right, bottom); + maxSlider.withXY(left + this.getWidth() + 5, top).withWH(this.getWidth(), this.getHeight()); + maxSlider.render(context, maxSlider.isMouseOver(mouseX, mouseY), mouseX, mouseY, deltaTicks, maxSlider.getX(), maxSlider.getY(), maxSlider.getX() + maxSlider.getWidth(), maxSlider.getY() + maxSlider.getHeight()); + } + } } diff --git a/src/main/java/dev/cigarette/helper/WeaponHelper.java b/src/main/java/dev/cigarette/helper/WeaponHelper.java new file mode 100644 index 00000000..4e8a59e2 --- /dev/null +++ b/src/main/java/dev/cigarette/helper/WeaponHelper.java @@ -0,0 +1,60 @@ +package dev.cigarette.helper; + +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.*; +import net.minecraft.registry.tag.ItemTags; + +public abstract class WeaponHelper { + public static boolean isRanged(ItemStack stack) { + if (stack == null || stack.isEmpty()) return false; + Item item = stack.getItem(); + return (item instanceof RangedWeaponItem) || (item instanceof ProjectileItem) || stack.isOf(Items.ENDER_PEARL); + } + + public static boolean isSword(ItemStack stack) { + return stack.isIn(ItemTags.SWORDS); + } + + public static int getBestRangedSlot(ClientPlayerEntity player) { + int best = -1; + for (int i = 0; i < 9; i++) { + ItemStack s = player.getInventory().getStack(i); + if (isRanged(s)) { + best = i; + // Prefer crossbow over bow if found later; for simplicity, last wins + } + } + return best; + } + + public static int getBestMeleeSlot(ClientPlayerEntity player) { + int best = -1; + int bestScore = Integer.MIN_VALUE; + for (int i = 0; i < 9; i++) { + ItemStack s = player.getInventory().getStack(i); + int score = getMeleeScore(s); + if (score > bestScore) { + bestScore = score; + best = i; + } + } + return (bestScore > Integer.MIN_VALUE) ? best : -1; + } + + public static int getMeleeScore(ItemStack s) { + if (s == null || s.isEmpty()) return Integer.MIN_VALUE; + if (s.isOf(Items.NETHERITE_SWORD)) return 90; + if (s.isOf(Items.DIAMOND_SWORD)) return 80; + if (s.isOf(Items.IRON_SWORD)) return 70; + if (s.isOf(Items.STONE_SWORD)) return 60; + if (s.isOf(Items.GOLDEN_SWORD)) return 55; + if (s.isOf(Items.WOODEN_SWORD)) return 50; + if (s.isOf(Items.NETHERITE_AXE)) return 75; + if (s.isOf(Items.DIAMOND_AXE)) return 68; + if (s.isOf(Items.IRON_AXE)) return 62; + if (s.isOf(Items.STONE_AXE)) return 56; + if (s.isOf(Items.GOLDEN_AXE)) return 50; + if (s.isOf(Items.WOODEN_AXE)) return 44; + return Integer.MIN_VALUE; + } +} diff --git a/src/main/java/dev/cigarette/lib/AimingL.java b/src/main/java/dev/cigarette/lib/AimingL.java new file mode 100644 index 00000000..cbdacfca --- /dev/null +++ b/src/main/java/dev/cigarette/lib/AimingL.java @@ -0,0 +1,253 @@ +package dev.cigarette.lib; + +import dev.cigarette.helper.WeaponHelper; +import dev.cigarette.mixin.ClientWorldAccessor; +import dev.cigarette.mixin.KeyBindingAccessor; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.network.PendingUpdateManager; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.world.RaycastContext; +import net.minecraft.entity.LivingEntity; +import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket; +import net.minecraft.network.packet.c2s.play.PlayerInteractItemC2SPacket; +import net.minecraft.util.Hand; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.NotNull; + +import javax.swing.text.html.parser.DTD; + +public class AimingL { + /** + * Compute yaw/pitch angles (in degrees) to look from 'from' to 'to'. + * Yaw is in [-180..180], pitch is in [-90..90]. + */ + public static float[] anglesFromTo(Vec3d from, Vec3d to) { + Vec3d d = to.subtract(from); + double lenSq = d.lengthSquared(); + if (!isFinite(d.x) || !isFinite(d.y) || !isFinite(d.z) || lenSq < 1.0e-12) { + return fallbackPE(); + } + Vec3d v = d.normalize(); + float yaw = (float) Math.toDegrees(Math.atan2(-v.x, v.z)); + float pitch = (float) Math.toDegrees(Math.asin(-v.y)); + yaw = sanitizeYaw(yaw); + pitch = sanitizePitch(pitch); + yaw = limitAccuracy(yaw, 100); + pitch = limitAccuracy(pitch, 100); + if (!Float.isFinite(yaw) || !Float.isFinite(pitch)) { + return fallbackPE(); + } + return new float[]{yaw, pitch}; + } + + private static float[] fallbackPE() { + ClientPlayerEntity p = MinecraftClient.getInstance().player; + float fallbackYaw = p != null ? MathHelper.wrapDegrees(p.getYaw()) : 0f; + float fallbackPitch = p != null ? MathHelper.clamp(p.getPitch(), -90f, 90f) : 0f; + return new float[]{fallbackYaw, fallbackPitch}; + } + + /** + * Generate a random signed double in the range [-max, -min] U [min, max]. + * If max <= 0, returns 0.0. + */ + public static double randomSigned(double min, double max) { + if (max <= 0) return 0.0; + double magnitude = min + (Math.random() * (max - min)); + return (Math.random() < 0.5 ? -1 : 1) * magnitude; + } + + /** + * Clamp point p to be inside the given box, with the given inset from each face. + */ + public static Vec3d clampToBox(Vec3d p, Box box, double inset) { + double minX = box.minX + inset, minY = box.minY + inset, minZ = box.minZ + inset; + double maxX = box.maxX - inset, maxY = box.maxY - inset, maxZ = box.maxZ - inset; + return new Vec3d( + MathHelper.clamp(p.x, minX, maxX), + MathHelper.clamp(p.y, minY, maxY), + MathHelper.clamp(p.z, minZ, maxZ) + ); + } + + /** + * Clamp point p to be inside the given box, with no inset. + */ + public static Vec3d clampToBox(Vec3d p, Box box) { + return clampToBox(p, box, 0.0); + } + + /** + * Get an aim point inside the target's hitbox, with optional jitter when attacking. + * If attackNow is false, returns the closest point on the hitbox to the player's eye position. + * If attackNow is true, returns a jittered point towards the center of the hitbox. + * Jitter is controlled by jitterMinDeg, jitterMaxDeg and jitterCapDegrees parameters. + */ + public static Vec3d getAimPointInsideHitbox(ClientPlayerEntity player, LivingEntity target, boolean attackNow, + double jitterMinDeg, double jitterMaxDeg, double jitterCapDegrees) { + Box box = target.getBoundingBox(); + Vec3d eye = player.getEyePos(); + double minX = box.minX, minY = box.minY, minZ = box.minZ; + double maxX = box.maxX, maxY = box.maxY, maxZ = box.maxZ; + + if (!attackNow) { + double vy = minY + (maxY - minY) * 0.5; + double vx = (minX + maxX) * 0.5; + double vz = (minZ + maxZ) * 0.5; + Vec3d center = new Vec3d(vx, vy, vz); + if (center.squaredDistanceTo(eye) < 1.0e-12) { + center = center.add(0.0, 1.0e-4, 0.0); + } + return center; + } + + double height = maxY - minY; + double region = Math.random(); + double pickY; + if (region < 0.6) { + pickY = minY + height * (0.4 + Math.random() * 0.2); + } else if (region < 0.85) { + pickY = minY + height * (0.78 + Math.random() * 0.18); + } else { + pickY = minY + height * (0.12 + Math.random() * 0.18); + } + + double cx = (minX + maxX) * 0.5; + double cz = (minZ + maxZ) * 0.5; + double halfX = (maxX - minX) * 0.5; + double halfZ = (maxZ - minZ) * 0.5; + double ox = (Math.random() - 0.5) * halfX * 0.6; + double oz = (Math.random() - 0.5) * halfZ * 0.6; + + Vec3d pick = new Vec3d(cx + ox, pickY, cz + oz); + if (pick.squaredDistanceTo(eye) < 1.0e-12) { + pick = pick.add(0.0, 1.0e-4, 0.0); + } + return clampToBox(pick, box, 1e-3); + } + + /** + * Start or stop sprinting based on the 'sprint' parameter. + * Only start sprinting if on ground and moving forward. + */ + public static void doSprint(boolean sprint) { + ClientPlayerEntity player = MinecraftClient.getInstance().player; + if (player == null) return; + KeyBinding forwardKey = KeyBinding.byId("key.forward"); + boolean forwardPressed = InputOverride.isActive ? InputOverride.forwardKey : (forwardKey != null && forwardKey.isPressed()); + if (!player.isOnGround() || !forwardPressed) return; + if (sprint) { + if (!player.isSprinting() && !player.isUsingItem() && !player.isSneaking()) { + player.setSprinting(true); + } + } else { + if (player.isSprinting()) { + player.setSprinting(false); + } + } + } + + /** + * Switch to the best PvP weapon based on distance to target. + * If distance > 7.5, prefer ranged weapon, else prefer melee weapon. + * If no suitable weapon found, do nothing. + * Returns true if a switch was made or already on best weapon, or false if no suitable weapon found. + */ + public static void switchToBestPvPWeapon(ClientPlayerEntity player, double distanceToTarget) { + if (player == null) return; + int current = player.getInventory().getSelectedSlot(); + int bestSlot; + if (distanceToTarget > 7.5) { + bestSlot = WeaponHelper.getBestRangedSlot(player); + if (bestSlot == -1) bestSlot = WeaponHelper.getBestMeleeSlot(player); + } else { + bestSlot = WeaponHelper.getBestMeleeSlot(player); + if (bestSlot == -1) bestSlot = WeaponHelper.getBestRangedSlot(player); + } + if (bestSlot == -1) return; + if (bestSlot == current) return; + player.getInventory().setSelectedSlot(bestSlot); + } + + /** + * Send a PlayerInteractItemC2SPacket with the given yaw/pitch and a new sequence number. + * Uses PendingUpdateManager to ensure proper sequencing. + */ + public static void sendAimPacket(ClientPlayerEntity player, float yaw, float pitch) { + if (player == null) return; + float cur = player.getYaw(); + float syaw = Float.isFinite(yaw) ? (cur + MathHelper.wrapDegrees(yaw - cur)) : cur; + float spitch = sanitizePitch(pitch); + player.setYaw(syaw); + player.setPitch(spitch); + } + + public static void lookAndAttack(ClientWorld world, ClientPlayerEntity player, LivingEntity target, float yaw, float pitch) { + if (player == null || target == null || world == null) return; + HitResult hitResult = MinecraftClient.getInstance().crosshairTarget; + KeyBinding attackKey = KeyBinding.byId("key.attack"); + if (attackKey == null) return; + KeyBindingAccessor attackKeyAccessor = (KeyBindingAccessor) attackKey; + + boolean shouldAttack = false; + + if (hitResult != null && hitResult.getType() == HitResult.Type.ENTITY) { + EntityHitResult entityHitResult = (EntityHitResult) hitResult; + Entity entity = entityHitResult.getEntity(); + if (entity instanceof LivingEntity livingEntity && livingEntity == target) { + if (livingEntity.hurtTime <= 1) shouldAttack = true; + } + } + + if (!shouldAttack) { + Vec3d eye = player.getEyePos(); + Vec3d toTarget = target.getBoundingBox().getCenter().subtract(eye); + double dist = toTarget.length(); + if (dist > 1e-6) { + double yawRad = Math.toRadians(yaw); + double pitchRad = Math.toRadians(pitch); + double dx = -Math.sin(yawRad) * Math.cos(pitchRad); + double dy = -Math.sin(pitchRad); + double dz = Math.cos(yawRad) * Math.cos(pitchRad); + Vec3d dir = new Vec3d(dx, dy, dz).normalize(); + Vec3d toNorm = toTarget.normalize(); + double dot = Math.max(-1.0, Math.min(1.0, dir.dotProduct(toNorm))); + double angleDeg = Math.toDegrees(Math.acos(dot)); + if (angleDeg <= 8.0 && dist <= 4.0 && target.hurtTime <= 1) { + shouldAttack = true; + } + } + } + + if (!shouldAttack) return; + + sendAimPacket(player, yaw, pitch); + + attackKeyAccessor.setTimesPressed(attackKeyAccessor.getTimesPressed() + 1); + MinecraftClient.getInstance().attackCooldown = 0; + } + + private static float sanitizeYaw(float yaw) { + return MathHelper.wrapDegrees(yaw); + } + + private static float sanitizePitch(float pitch) { + return MathHelper.clamp(pitch, -90f, 90f); + } + + private static float limitAccuracy(float v, int accuracy) { + if (accuracy <= 0) return v; + return (float) (Math.round(v * accuracy) / (double) accuracy); + } + + private static boolean isFinite(double v) { + return Double.isFinite(v); + } +} diff --git a/src/main/java/dev/cigarette/lib/PlayerEntityL.java b/src/main/java/dev/cigarette/lib/PlayerEntityL.java index e4e8b8ff..bdf562d7 100644 --- a/src/main/java/dev/cigarette/lib/PlayerEntityL.java +++ b/src/main/java/dev/cigarette/lib/PlayerEntityL.java @@ -1,11 +1,16 @@ package dev.cigarette.lib; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BowItem; import net.minecraft.item.ItemStack; +import net.minecraft.registry.tag.ItemTags; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; -import org.jetbrains.annotations.Nullable; public class PlayerEntityL { + /* + * Returns yaw and pitch to look in the direction of the given vector. + */ public static float[] getRotationVectorInDirection(Vec3d vector) { Vec3d normalized = vector.normalize(); double pitchRadians = Math.asin(-normalized.y); @@ -17,15 +22,16 @@ public static float[] getRotationVectorInDirection(Vec3d vector) { return new float[]{yaw, pitch}; } + /* + * Sets the player's yaw and pitch to look in the direction of the given vector. + */ public static void setRotationVector(PlayerEntity player, Vec3d vector) { float[] yawPitch = getRotationVectorInDirection(vector); - player.setYaw(yawPitch[0]); - player.setPitch(yawPitch[1]); + float currentYaw = player.getYaw(); + float continuousYaw = currentYaw + MathHelper.wrapDegrees(yawPitch[0] - currentYaw); + player.setYaw(continuousYaw); + float clampedPitch = MathHelper.clamp(yawPitch[1], -90f, 90f); + player.setPitch(clampedPitch); } - public static float angleBetween(float yaw, float pitch, float yaw2, float pitch2) { - float yawDiff = Math.abs(((yaw2 - yaw + 180) % 360) - 180); - float pitchDiff = Math.abs(pitch2 - pitch); - return (float) Math.sqrt(yawDiff * yawDiff + pitchDiff * pitchDiff) % 360; - } } diff --git a/src/main/java/dev/cigarette/lib/ServerL.java b/src/main/java/dev/cigarette/lib/ServerL.java new file mode 100644 index 00000000..ff60e207 --- /dev/null +++ b/src/main/java/dev/cigarette/lib/ServerL.java @@ -0,0 +1,85 @@ +package dev.cigarette.lib; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.component.ComponentType; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.DyedColorComponent; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Formatting; + +import java.util.Objects; + +public class ServerL { + /* + * Returns true if the two players are on the same team. + * Uses common Minecraft server logic. + * All individual checks are also exposed in ServerL. + */ + public static boolean playerOnSameTeam(PlayerEntity player, PlayerEntity other) { + if (player == null || other == null) return false; + if (player.getScoreboardTeam() != null && other.getScoreboardTeam() != null && player.getScoreboardTeam().isEqual(other.getScoreboardTeam())) + return true; + return getScoreboardColor(player) != -1 && getScoreboardColor(player) == getScoreboardColor(other) || + getArmorColor(player) != -1 && getArmorColor(player) == getArmorColor(other) || + getNameColor(player) != -1 && getNameColor(player) == getNameColor(other); + } + + public static boolean playerOnSameTeam(ServerPlayerEntity player, ServerPlayerEntity other) { + return playerOnSameTeam((PlayerEntity) player, (PlayerEntity) other); + } + + public static int getNameColor(PlayerEntity player) { + try { + if (player == null || player.getDisplayName() == null) return -1; + var style = player.getDisplayName().getStyle(); + if (style.getColor() == null) return -1; + return style.getColor().getRgb(); + } catch (Exception ignored) { + return -1; + } + } + + public static int getScoreboardColor(PlayerEntity player) { + if (player == null || player.getScoreboardTeam() == null) return -1; + try { + Formatting color = player.getScoreboardTeam().getColor(); + if (color == null || color.getColorValue() == null) return -1; + return color.getColorValue(); + } catch (Exception ignored) { + return -1; + } + } + + public static int getArmorColor(PlayerEntity player) { + if (player == null || player.getInventory() == null) return -1; + + ItemStack boots = player.getInventory().getStack(36); + ItemStack leggings = player.getInventory().getStack(37); + + // ItemStack chestplate = player.getInventory().getStack(38); + // ItemStack helmet = player.getInventory().getStack(39); + + DyedColorComponent b = getDyedColorComponent(boots); + DyedColorComponent l = getDyedColorComponent(leggings); + + if (b != null && l != null && b.rgb() == l.rgb()) { + return b.rgb(); + } + return -1; + } + + private static DyedColorComponent getDyedColorComponent(ItemStack stack) { + if (stack == null) return null; + ComponentType type = DataComponentTypes.DYED_COLOR; + return stack.getItem().getComponents().get(type); + } + + public static ServerPlayerEntity getPlayer(ClientPlayerEntity player) { + MinecraftClient mc = MinecraftClient.getInstance(); + if (mc == null || mc.getServer() == null || player == null) return null; // mc.getServer() null on multiplayer clients + return mc.getServer().getPlayerManager().getPlayer(player.getUuid()); + } +} \ No newline at end of file diff --git a/src/main/java/dev/cigarette/lib/WeaponSelector.java b/src/main/java/dev/cigarette/lib/WeaponSelector.java index 30ca9b27..9f1cf480 100644 --- a/src/main/java/dev/cigarette/lib/WeaponSelector.java +++ b/src/main/java/dev/cigarette/lib/WeaponSelector.java @@ -3,14 +3,15 @@ import dev.cigarette.agent.ZombiesAgent; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; +import net.minecraft.item.*; import net.minecraft.item.tooltip.TooltipType; import net.minecraft.text.Text; import org.jetbrains.annotations.Nullable; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; // Import ConcurrentHashMap +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -248,4 +249,5 @@ public static void addCooldown(int slotIndex) { } } } -} \ No newline at end of file +} + diff --git a/src/main/java/dev/cigarette/mixin/EntityMixin.java b/src/main/java/dev/cigarette/mixin/EntityMixin.java index e3d4b26e..49bea5dd 100644 --- a/src/main/java/dev/cigarette/mixin/EntityMixin.java +++ b/src/main/java/dev/cigarette/mixin/EntityMixin.java @@ -5,6 +5,7 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.UUID; @@ -32,4 +33,15 @@ private void getTeamColorValue(CallbackInfoReturnable cir) { cir.setReturnValue(Glow.getGlowColor(uuid)); } } + + @Inject(method = "setYaw", at = @At("HEAD")) + public void setYaw(float yaw, CallbackInfo ci) { + if (yaw > 360) yaw = yaw % 360; + if (yaw < 0) yaw = 360 + (yaw % 360); + } + @Inject(method = "setPitch", at = @At("HEAD")) + public void setPitch(float pitch, CallbackInfo ci) { + if (pitch > 90) pitch = 90; + if (pitch < -90) pitch = -90; + } } diff --git a/src/main/java/dev/cigarette/module/bedwars/AutoBlockIn.java b/src/main/java/dev/cigarette/module/bedwars/AutoBlockIn.java index ee538958..8a9f81d4 100644 --- a/src/main/java/dev/cigarette/module/bedwars/AutoBlockIn.java +++ b/src/main/java/dev/cigarette/module/bedwars/AutoBlockIn.java @@ -59,8 +59,10 @@ private void enable(@NotNull ClientPlayerEntity player) { private void disable(@NotNull ClientPlayerEntity player) { running = false; - player.setYaw(originalYaw); - player.setPitch(originalPitch); + float curYaw = player.getYaw(); + float smoothYaw = curYaw + net.minecraft.util.math.MathHelper.wrapDegrees(originalYaw - curYaw); + player.setYaw(smoothYaw); + player.setPitch(net.minecraft.util.math.MathHelper.clamp(originalPitch, -90f, 90f)); previousVector = null; } diff --git a/src/main/java/dev/cigarette/module/bedwars/Bridger.java b/src/main/java/dev/cigarette/module/bedwars/Bridger.java index 6d3415b8..440b2a96 100644 --- a/src/main/java/dev/cigarette/module/bedwars/Bridger.java +++ b/src/main/java/dev/cigarette/module/bedwars/Bridger.java @@ -16,6 +16,7 @@ import net.minecraft.util.hit.HitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; +import net.minecraft.util.math.MathHelper; import org.jetbrains.annotations.NotNull; public class Bridger extends TickModule { @@ -124,11 +125,17 @@ private void rightClick(int times) { useAccessor.setTimesPressed(useAccessor.getTimesPressed() + times); } - private void enable(BridgeType type, float yaw) { + private float continuousYawFor(ClientPlayerEntity player, float desiredYaw) { + float current = player.getYaw(); + float delta = MathHelper.wrapDegrees(desiredYaw - current); + return current + delta; + } + + private void enable(BridgeType type, ClientPlayerEntity player, float yaw) { this.bridgeType = type; InputOverride.isActive = true; InputOverride.pitch = 77.0f; - InputOverride.yaw = yaw; + InputOverride.yaw = continuousYawFor(player, yaw); InputOverride.sneakKey = true; InputOverride.backKey = true; InputOverride.leftKey = leftKey.isPressed(); @@ -169,11 +176,11 @@ protected void onEnabledTick(MinecraftClient client, @NotNull ClientWorld world, if (isStraightBridge()) { if (!toggleStraight.getRawState()) return; - enable(BridgeType.STRAIGHT, straightBridgeYaw(placingFace)); + enable(BridgeType.STRAIGHT, player, straightBridgeYaw(placingFace)); } else if (isDiagonalBridge()) { if (!toggleDiagonal.getRawState()) return; boolean godBridge = toggleDiagonalGod.getRawState() && isDiagonalGodBridge(playerPos, blockPos); - enable(godBridge ? BridgeType.DIAGONAL_GOD : BridgeType.DIAGONAL, diagonalBridgeYaw(player)); + enable(godBridge ? BridgeType.DIAGONAL_GOD : BridgeType.DIAGONAL, player, diagonalBridgeYaw(player)); } } else { if (!sneakKey.isPressed() || !backwardsKey.isPressed() || !rightClickKey.isPressed()) { @@ -263,4 +270,5 @@ public int getId() { } */ } -} \ No newline at end of file +} + diff --git a/src/main/java/dev/cigarette/module/combat/PlayerAimbot.java b/src/main/java/dev/cigarette/module/combat/PlayerAimbot.java new file mode 100644 index 00000000..68354bc0 --- /dev/null +++ b/src/main/java/dev/cigarette/module/combat/PlayerAimbot.java @@ -0,0 +1,491 @@ +package dev.cigarette.module.combat; + +import dev.cigarette.GameDetector; +import dev.cigarette.agent.MurderMysteryAgent; +import dev.cigarette.gui.widget.SliderWidget; +import dev.cigarette.gui.widget.ToggleWidget; +import dev.cigarette.gui.widget.ToggleKeybindWidget; +import dev.cigarette.lib.AimingL; +import dev.cigarette.lib.ServerL; +import dev.cigarette.lib.WorldL; +import dev.cigarette.module.TickModule; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.LivingEntity; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.stream.Stream; + +public class PlayerAimbot extends TickModule { + public static final PlayerAimbot INSTANCE = new PlayerAimbot("combat.playerAimbot", "PlayerAimbot", "Automatically aims at players. The legitest killaura."); + + private final ToggleWidget autoAttack = new ToggleWidget("Auto Attack", "Automatically hits players").withDefaultState(true); + public final SliderWidget smoothAim = new SliderWidget("Smooth Aim", "How quickly to snap to target in ticks").withBounds(1, 5, 20); + public final ToggleWidget wTap = new ToggleWidget("W-Tap", "Automatically sprint before attacking").withDefaultState(false); + + public final SliderWidget attackCps = new SliderWidget("Attack CPS", "Clicks per second for auto attack").withBounds(1, 8, 15); + + public final ToggleWidget ignoreTeammates = new ToggleWidget("Ignore Teammates", "Don't target players on your team").withDefaultState(true); + + private final ToggleKeybindWidget lockOnKeybind = new ToggleKeybindWidget( + "Lock-On Trigger", "Press key to lock to best target. Releases only when target dies" + ).withDefaultState(false); + + public final ToggleWidget murderMysteryMode = new ToggleWidget("Murder Mystery", "Prefer murderer and detective targets").withDefaultState(false); + public final ToggleWidget detectiveAim = new ToggleWidget("Detective Aim", "Aim at a detective when no Murderer is available (or you are the mur)").withDefaultState(true); + + public final SliderWidget jitterViolence = new SliderWidget("Jitter Aggression", "Scale of aim jitter (0 disables)").withBounds(0.0, 1.0, 3.0).withAccuracy(2); + public final SliderWidget driftViolence = new SliderWidget("Drift Aggression", "Scale of slow drift vs jitter").withBounds(0.0, 0.6, 2.0).withAccuracy(2); + public final SliderWidget aimToleranceDeg = new SliderWidget("Aim Tolerance (deg)", "Max angular distance (hypot of yaw/pitch) off for attack").withBounds(0.5, 2.0, 6.0).withAccuracy(1); + + public final SliderWidget smoothingMultiplier = new SliderWidget("Smoothing Multiplier", "Scales duration based on angle").withBounds(0.5, 1.0, 3.0).withAccuracy(2); + public final SliderWidget bezierInfluence = new SliderWidget("Bezier Influence", "0 = linear, 1 = full bezier curve").withBounds(0.0, 1.0, 1.0).withAccuracy(2); + public final SliderWidget controlJitterScale = new SliderWidget("Control Jitter", "Randomness of control points").withBounds(0.0, 1.0, 3.0).withAccuracy(2); + public final SliderWidget interpolationMode = new SliderWidget("Interpolation Mode", "0=Linear 1=Cubic 2=Cos 3=Smoothstep").withBounds(0, 1, 3).withAccuracy(0); + + public final SliderWidget aimRange = new SliderWidget("Aim Range", "Maximum range to target players").withBounds(3, 6, 20).withAccuracy(1); + + public final ToggleWidget prediction = new ToggleWidget("Prediction", "Predict target position").withDefaultState(false); + public final SliderWidget predictionTicks = new SliderWidget("Prediction Ticks", "Ticks ahead to predict").withBounds(0, 5, 20).withAccuracy(1); + public final SliderWidget engageAimAngle = new SliderWidget("Engage Angle (deg)", "Only begin aim plan when target angle gap >= this; also scales prediction").withBounds(1.0, 6.0, 30.0).withAccuracy(1); + + private PlayerAimbot(String id, String name, String tooltip) { + super(ToggleWidget::module, id, name, tooltip); + this.setChildren(autoAttack, smoothAim, aimRange, prediction, predictionTicks, engageAimAngle, wTap, attackCps, ignoreTeammates, lockOnKeybind, + murderMysteryMode, detectiveAim, + jitterViolence, driftViolence, aimToleranceDeg, smoothingMultiplier, bezierInfluence, controlJitterScale, interpolationMode); + autoAttack.registerConfigKey(id + ".autoAttack"); + smoothAim.registerConfigKey(id + ".smoothAim"); + aimRange.registerConfigKey(id + ".aimRange"); + prediction.registerConfigKey(id + ".prediction"); + predictionTicks.registerConfigKey(id + ".predictionTicks"); + engageAimAngle.registerConfigKey(id + ".engageAimAngle"); + wTap.registerConfigKey(id + ".wTap"); + attackCps.registerConfigKey(id + ".attackCps"); + ignoreTeammates.registerConfigKey(id + ".ignoreTeammates"); + lockOnKeybind.registerConfigKey(id + ".lockOnKeybind"); + murderMysteryMode.registerConfigKey(id + ".murderMysteryMode"); + detectiveAim.registerConfigKey(id + ".detectiveAim"); + jitterViolence.registerConfigKey(id + ".jitterViolence"); + driftViolence.registerConfigKey(id + ".driftViolence"); + aimToleranceDeg.registerConfigKey(id + ".aimToleranceDeg"); + smoothingMultiplier.registerConfigKey(id + ".smoothingMultiplier"); + bezierInfluence.registerConfigKey(id + ".bezierInfluence"); + controlJitterScale.registerConfigKey(id + ".controlJitterScale"); + interpolationMode.registerConfigKey(id + ".interpolationMode"); + } + + // Bezier aim plan state + public @Nullable LivingEntity activeTarget = null; + private @Nullable UUID activeTargetId = null; + private int planTicksTotal = 0; + private int planTicksElapsed = 0; + private float startYaw = 0f, startPitch = 0f; + private float c1Yaw = 0f, c1Pitch = 0f; + private float c2Yaw = 0f, c2Pitch = 0f; + private float endYaw = 0f, endPitch = 0f; + private final Random rng = new Random(); + + // CPS scheduling + private int ticksUntilNextAttack = 0; + + // Reach assumption (entityAttackRange not exposed here) + private static final double MELEE_REACH = 3.2; + + private boolean lastAppliedValid = false; + private float lastAppliedYaw = 0f, lastAppliedPitch = 0f; + + private boolean lockOnEngaged = false; + private static final float SOFT_REPLAN_ANGLE_DEG = 12f; + + @Override + protected void onEnabledTick(MinecraftClient client, @NotNull ClientWorld world, @NotNull ClientPlayerEntity player) { + if (MinecraftClient.getInstance().currentScreen != null) return; + boolean lockOnMode = lockOnKeybind.getRawState(); + if (!lockOnMode && lockOnEngaged) { lockOnEngaged = false; clearPlan(); } + LivingEntity target = null; + + if (lockOnMode) { + if (!lockOnEngaged && lockOnKeybind.getKeybind().wasPressed()) { + AbstractClientPlayerEntity best = getBestTargetFor(player); + if (best != null) { + lockOnEngaged = true; activeTarget = best; activeTargetId = best.getUuid(); planTicksTotal = 0; planTicksElapsed = 0; + } + } + if (lockOnEngaged) { + if (activeTarget != null && activeTarget.isAlive() && !activeTarget.isRemoved() && player.squaredDistanceTo(activeTarget) <= Math.pow(aimRange.getRawState(), 2)) { + target = activeTarget; + } else { lockOnEngaged = false; clearPlan(); return; } + } else { clearPlan(); return; } + } else { target = pickOrValidateTarget(world, player); } + + if (target == null) { clearPlan(); return; } + + double angleGap = computeAngleGap(player, target); + double engageThresh = Math.max(0.5, engageAimAngle.getRawState()); + int basePred = (int) Math.round(predictionTicks.getRawState()); + int dynamicPred = basePred; + if (prediction.getRawState() && angleGap >= engageThresh) { + double factor = (angleGap - engageThresh) / engageThresh; + int extra = (int) Math.min(10, Math.max(0, Math.round(factor * 4))); + dynamicPred = Math.min(30, basePred + extra); + } + + Vec3d aimPoint = computeAimPoint(player, target, false, dynamicPred); + float[] targetAngles = AimingL.anglesFromTo(player.getEyePos(), aimPoint); + float curYaw = getContinuousYaw(player); + float curPitch = MathHelper.clamp(player.getPitch(), -90f, 90f); + + boolean planActive = isPlanActive(); + boolean sameTarget = Objects.equals(target.getUuid(), activeTargetId); + float dyawToEnd = Math.abs(MathHelper.wrapDegrees(endYaw - targetAngles[0])); + float dpitchToEnd = Math.abs(endPitch - targetAngles[1]); + + boolean largeChange = dyawToEnd > SOFT_REPLAN_ANGLE_DEG || dpitchToEnd > SOFT_REPLAN_ANGLE_DEG; + boolean minorChange = (dyawToEnd > 0.2f || dpitchToEnd > 0.2f) && !largeChange; + + if (!planActive || !sameTarget) { + if (angleGap >= engageThresh || !planActive) { + buildBezierPlan(curYaw, curPitch, targetAngles[0], targetAngles[1]); + activeTarget = target; activeTargetId = target.getUuid(); + } + } else { + if (largeChange) { + buildBezierPlan(curYaw, curPitch, targetAngles[0], targetAngles[1]); + } else if (minorChange) { + updatePlanEndpoint(targetAngles[0], targetAngles[1]); + } + } + + if (!isPlanActive() && angleGap < engageThresh) { + float snapYaw = unwrapTowards(targetAngles[0], curYaw); + player.setYaw(snapYaw); + player.setPitch(targetAngles[1]); + lastAppliedYaw = snapYaw; lastAppliedPitch = targetAngles[1]; lastAppliedValid = true; + } else { + float[] stepAngles = evalPlanStep(); + player.setYaw(stepAngles[0]); + player.setPitch(stepAngles[1]); + lastAppliedYaw = stepAngles[0]; lastAppliedPitch = stepAngles[1]; lastAppliedValid = true; + } + + if (autoAttack.getRawState()) { + if (ticksUntilNextAttack > 0) ticksUntilNextAttack--; + boolean withinRange = player.distanceTo(target) <= MELEE_REACH; + boolean aimAligned = aimAlignmentOk(player, target); + if (withinRange && aimAligned && ticksUntilNextAttack <= 0) { + if (wTap.getRawState()) AimingL.doSprint(true); + Vec3d atkAim = computeAimPoint(player, target, true, dynamicPred); + float[] atkAngles = AimingL.anglesFromTo(player.getEyePos(), atkAim); + AimingL.lookAndAttack(world, player, target, atkAngles[0], atkAngles[1]); + buildBezierPlan(atkAngles[0], atkAngles[1], targetAngles[0], targetAngles[1]); + lastAppliedYaw = atkAngles[0]; lastAppliedPitch = atkAngles[1]; + planTicksElapsed = Math.min(1, planTicksElapsed); + scheduleNextAttack(); + } + } + } + + private @Nullable LivingEntity pickOrValidateTarget(ClientWorld world, ClientPlayerEntity player) { + if (activeTarget != null && activeTarget.isAlive() && !activeTarget.isRemoved() + && player.squaredDistanceTo(activeTarget) <= Math.pow(aimRange.getRawState(), 2)) { + if (shouldFilterTeammates() && activeTarget instanceof AbstractClientPlayerEntity acp + && ServerL.playerOnSameTeam(player, acp)) { + return getBestTargetFor(player); + } + return activeTarget; + } + + return getBestTargetFor(player); + } + + private double scoreTarget(ClientPlayerEntity self, float selfYaw, AbstractClientPlayerEntity other) { + double d2 = self.squaredDistanceTo(other); + if (d2 < 0.01) d2 = 0.01; + float[] angles = AimingL.anglesFromTo(self.getEyePos(), other.getEyePos().add(0, other.getHeight() * 0.5, 0)); + float yawDiff = MathHelper.wrapDegrees(angles[0] - selfYaw); + float pitchDiff = angles[1] - self.getPitch(); + double angleDiff = Math.hypot(yawDiff, pitchDiff); + if (angleDiff < 0.1) angleDiff = 0.1; + return d2 * angleDiff; + } + + private @Nullable AbstractClientPlayerEntity detectMurderer(ClientPlayerEntity self) { + List players = self.clientWorld.getPlayers(); + Stream stream = players.stream() + .filter(p -> p != self) + .filter(p -> p.isAlive() && !p.isRemoved() && !p.isSpectator()) + .filter(p -> self.squaredDistanceTo(p) <= Math.pow(aimRange.getRawState(), 2)); + if (shouldFilterTeammates()) { + stream = stream.filter(p -> !ServerL.playerOnSameTeam(self, p)); + } + return stream + .filter( + p -> MurderMysteryAgent.getRole(p) == MurderMysteryAgent.PersistentPlayer.Role.MURDERER) + .min(Comparator.comparingDouble(p -> scoreTarget(self, self.getYaw(), p))) + .orElse(null); + } + + private void scheduleNextAttack() { + double cps = Math.max(1.0, attackCps.getRawState()); + double idealTicks = 20.0 / cps; + double jitter = idealTicks * (0.15 + rng.nextDouble() * 0.20); + ticksUntilNextAttack = (int) Math.max(1, Math.round(idealTicks + (rng.nextBoolean() ? jitter : -jitter * 0.5))); + } + + private boolean aimAlignmentOk(ClientPlayerEntity player, LivingEntity target) { + Vec3d aimPoint = computeAimPoint(player, target); + Vec3d lookVec = player.getRotationVec(1.0F); + Vec3d toTarget = aimPoint.subtract(player.getEyePos()); + if (toTarget.lengthSquared() == 0) return true; + lookVec = lookVec.normalize(); + Vec3d toNorm = toTarget.normalize(); + double dot = Math.max(-1.0, Math.min(1.0, lookVec.dotProduct(toNorm))); + double angleDeg = Math.toDegrees(Math.acos(dot)); + double tol = Math.max(0.1, aimToleranceDeg.getRawState()); + return angleDeg <= tol; + } + + private void updatePlanEndpoint(float newYaw, float newPitch) { + endYaw = unwrapTowards(newYaw, startYaw); + endPitch = MathHelper.clamp(newPitch, -90f, 90f); + float minPitch = Math.min(startPitch, endPitch); + float maxPitch = Math.max(startPitch, endPitch); + c1Pitch = MathHelper.clamp(c1Pitch, minPitch, maxPitch); + c2Pitch = MathHelper.clamp(c2Pitch, minPitch, maxPitch); + } + + private float[] evalPlanStep() { + if (!isPlanActive()) return new float[]{startYaw, startPitch}; + planTicksElapsed = Math.min(planTicksElapsed + 1, planTicksTotal); + float t = planTicksTotal == 0 ? 1f : (float) planTicksElapsed / (float) planTicksTotal; + float et = applyInterpolation(t); + float linYaw = startYaw + (endYaw - startYaw) * et; + float linPitch = startPitch + (endPitch - startPitch) * et; + float bezYaw = cubicBezier(startYaw, c1Yaw, c2Yaw, endYaw, et); + float bezPitch = cubicBezier(startPitch, c1Pitch, c2Pitch, endPitch, et); + float inf = (float) Math.max(0.0, Math.min(1.0, bezierInfluence.getRawState())); + float yaw = linYaw + (bezYaw - linYaw) * inf; + float pitch = linPitch + (bezPitch - linPitch) * inf; + + float angleSpan = Math.abs(MathHelper.wrapDegrees(startYaw - endYaw)) + Math.abs(endPitch - startPitch); + float baseJitter = Math.max(0.0f, Math.min(0.4f, angleSpan * 0.0025f)); + float jitterScale = (float) Math.max(0.0, jitterViolence.getRawState()); + float driftScale = (float) Math.max(0.0, driftViolence.getRawState()); + float jitterMagYaw = baseJitter * jitterScale; + float jitterMagPitch = baseJitter * 0.55f * jitterScale; + float driftMagYaw = jitterMagYaw * 0.5f * driftScale; + float driftMagPitch = jitterMagPitch * 0.5f * driftScale; + float fineJitterYaw = (float) (rng.nextGaussian() * jitterMagYaw * (1.0 - et)); + float fineJitterPitch = (float) (rng.nextGaussian() * jitterMagPitch * (1.0 - et)); + float driftYaw = (float) (rng.nextGaussian() * driftMagYaw * 0.2); + float driftPitch = (float) (rng.nextGaussian() * driftMagPitch * 0.2); + + float outYaw = yaw + fineJitterYaw + driftYaw; + float outPitch = pitch + fineJitterPitch + driftPitch; + float minBand = Math.min(startPitch, endPitch) - 2.0f; + float maxBand = Math.max(startPitch, endPitch) + 2.0f; + outPitch = MathHelper.clamp(outPitch, minBand, maxBand); + outPitch = MathHelper.clamp(outPitch, -90f, 90f); + return new float[]{outYaw, outPitch}; + } + + private void buildBezierPlan(float curYaw, float curPitch, float targetYaw, float targetPitch) { + startYaw = curYaw; + startPitch = curPitch; + float tYaw = unwrapTowards(targetYaw, curYaw); + endYaw = tYaw; + endPitch = MathHelper.clamp(targetPitch, -90f, 90f); + + float yawDelta = endYaw - startYaw; + float pitchDelta = endPitch - startPitch; + float angleMag = Math.abs(yawDelta) + Math.abs(pitchDelta); + + int baseTicks = (int) Math.max(1, Math.round(smoothAim.getRawState())); + double mult = Math.max(0.1, smoothingMultiplier.getRawState()); + planTicksTotal = (int) Math.max(baseTicks, Math.min(baseTicks * 4L, Math.round(baseTicks * mult * (0.5 + angleMag / 45f)))); + planTicksElapsed = 0; + + float c1t = 0.30f + (float) rng.nextDouble() * 0.15f; + float c2t = 0.70f - (float) rng.nextDouble() * 0.15f; + float ctrlJitter = (float) (Math.max(0.0f, Math.min(3.5f, angleMag * 0.05f)) * Math.max(0.0, controlJitterScale.getRawState())); + c1Yaw = startYaw + yawDelta * c1t + (float) (rng.nextGaussian() * ctrlJitter); + c2Yaw = startYaw + yawDelta * c2t + (float) (rng.nextGaussian() * ctrlJitter); + c1Pitch = startPitch + pitchDelta * c1t + (float) (rng.nextGaussian() * ctrlJitter * 0.6f); + c2Pitch = startPitch + pitchDelta * c2t + (float) (rng.nextGaussian() * ctrlJitter * 0.6f); + + float minPitch = Math.min(startPitch, endPitch); + float maxPitch = Math.max(startPitch, endPitch); + c1Pitch = MathHelper.clamp(c1Pitch, minPitch, maxPitch); + c2Pitch = MathHelper.clamp(c2Pitch, minPitch, maxPitch); + + c1Yaw = unwrapTowards(c1Yaw, startYaw); + c2Yaw = unwrapTowards(c2Yaw, startYaw); + endYaw = unwrapTowards(endYaw, startYaw); + } + + private float applyInterpolation(float t) { + int mode = (int) Math.round(interpolationMode.getRawState()); + switch (mode) { + case 0: // Linear + return t; + case 1: // EaseInOutCubic + return easeInOutCubic(t); + case 2: // Cosine + return (float) (0.5 - 0.5 * Math.cos(Math.PI * t)); + case 3: // Smoothstep + return t * t * (3f - 2f * t); + default: + return t; + } + } + + private boolean isPlanActive() { + return planTicksElapsed < planTicksTotal; + } + + private double computeAngleGap(ClientPlayerEntity player, LivingEntity target) { + Vec3d eye = player.getEyePos(); + Vec3d to = target.getBoundingBox().getCenter().subtract(eye); + if (to.lengthSquared() == 0) return 0.0; + Vec3d look = player.getRotationVec(1.0F).normalize(); + Vec3d dir = to.normalize(); + double dot = Math.max(-1, Math.min(1, look.dotProduct(dir))); + return Math.toDegrees(Math.acos(dot)); + } + + private boolean planTargetChangedSignificantly(float[] targetAngles) { + float dyaw = Math.abs(MathHelper.wrapDegrees(endYaw - targetAngles[0])); + float dpitch = Math.abs(endPitch - targetAngles[1]); + double thresh = Math.max(1.0, engageAimAngle.getRawState()); + return dyaw > thresh || dpitch > thresh; + } + + private void clearPlan() { + activeTarget = null; + activeTargetId = null; + planTicksTotal = 0; + planTicksElapsed = 0; + } + + private static float cubicBezier(float p0, float p1, float p2, float p3, float t) { + float u = 1f - t; + return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3; + } + + private static float easeInOutCubic(float t) { + return t < 0.5f ? 4f * t * t * t : 1f - (float) Math.pow(-2f * t + 2f, 3f) / 2f; + } + + private static float unwrapTowards(float target, float reference) { + float t = target; + while (t - reference > 180f) t -= 360f; + while (t - reference < -180f) t += 360f; + return t; + } + + private float getContinuousYaw(ClientPlayerEntity player) { + float wrappedCurrent = MathHelper.wrapDegrees(player.getYaw()); + return lastAppliedValid ? unwrapTowards(wrappedCurrent, lastAppliedYaw) : wrappedCurrent; + } + + private Vec3d computeAimPoint(ClientPlayerEntity player, LivingEntity target, boolean attackNow, int dynamicTicks) { + if (prediction.getRawState()) { + Vec3d vel = target.getVelocity(); + int ticks = Math.max(0, dynamicTicks); + double px = target.getX() + vel.x * ticks; + double pz = target.getZ() + vel.z * ticks; + + double baseY = target.getY(); + double height = target.getHeight(); + double py; + if (target.isOnGround() || Math.abs(vel.y) < 0.05 || ticks == 0) { + py = baseY + height * 0.5; + } else { + double g = 0.08; + double vy = vel.y; + double t = ticks; + double predictedY = baseY + (vy * t) - 0.5 * g * t * t; + double minY = baseY + height * 0.25; + double maxY = baseY + height * 0.85; + py = MathHelper.clamp(predictedY, minY, maxY); + } + return new Vec3d(px, py, pz); + } else { + return AimingL.getAimPointInsideHitbox(player, target, attackNow, 0.1, 0.6, 2.5); + } + } + + private Vec3d computeAimPoint(ClientPlayerEntity player, LivingEntity target) { + return computeAimPoint(player, target, false, (int) Math.round(predictionTicks.getRawState())); + } + + private boolean isMurderMysteryActive() { + return murderMysteryMode.getRawState(); + } + + private boolean shouldFilterTeammates() { + return !isMurderMysteryActive() && ignoreTeammates.getRawState(); + } + + private AbstractClientPlayerEntity detectDetective(ClientPlayerEntity self) { + if (!detectiveAim.getRawState()) return null; + List players = self.clientWorld.getPlayers(); + Stream stream = players.stream() + .filter(p -> p != self) + .filter(p -> p.isAlive() && !p.isRemoved() && !p.isSpectator()) + .filter(p -> self.squaredDistanceTo(p) <= Math.pow(aimRange.getRawState(), 2)); + if (shouldFilterTeammates()) { + stream = stream.filter(p -> !ServerL.playerOnSameTeam(self, p)); + } + return stream + .filter( + p -> MurderMysteryAgent.getRole(p) == MurderMysteryAgent.PersistentPlayer.Role.DETECTIVE) + .min(Comparator.comparingDouble(p -> scoreTarget(self, self.getYaw(), p))) + .orElse(null); + } + + public static @Nullable AbstractClientPlayerEntity getBestTargetFor(ClientPlayerEntity self) { + if (self == null || self.clientWorld == null) return null; + ClientWorld world = self.clientWorld; + float curYaw = self.getYaw(); + double range2 = Math.pow(INSTANCE.aimRange.getRawState(), 2); + + if (INSTANCE.isMurderMysteryActive()) { + AbstractClientPlayerEntity mm = INSTANCE.detectMurderer(self); + if (mm != null) return mm; + AbstractClientPlayerEntity det = INSTANCE.detectDetective(self); + if (det != null) return det; + return null; + } + + boolean filterTeams = !INSTANCE.isMurderMysteryActive() && INSTANCE.ignoreTeammates.getRawState(); + return world.getPlayers().stream() + .filter(p -> p != self) + .filter(p -> self.squaredDistanceTo(p) <= range2) + .filter(p -> !filterTeams || !ServerL.playerOnSameTeam(self, p)) + .filter(botPredicate) + .min(Comparator.comparingDouble(p -> INSTANCE.scoreTarget(self, curYaw, p))) + .orElse(null); + } + + public static final java.util.function.Predicate botPredicate = p -> + !(p.isSleeping() || p.isSpectator() || p.isInvisible() || p.isDead() || p.age < 20 || !WorldL.isRealPlayer(p)); + + @Override + protected void onDisabledTick(MinecraftClient client) { + clearPlan(); + lockOnEngaged = false; + } + + @Override + public boolean inValidGame() { + return GameDetector.rootGame != GameDetector.ParentGame.ZOMBIES; + } +} diff --git a/src/main/java/dev/cigarette/module/murdermystery/AutoBow.java b/src/main/java/dev/cigarette/module/murdermystery/AutoBow.java new file mode 100644 index 00000000..7813b5fe --- /dev/null +++ b/src/main/java/dev/cigarette/module/murdermystery/AutoBow.java @@ -0,0 +1,346 @@ +package dev.cigarette.module.murdermystery; + +import dev.cigarette.agent.MurderMysteryAgent; +import dev.cigarette.gui.widget.SliderWidget; +import dev.cigarette.gui.widget.ToggleWidget; +import dev.cigarette.mixin.KeyBindingAccessor; +import dev.cigarette.module.TickModule; +import dev.cigarette.module.combat.AutoClicker; +import dev.cigarette.module.combat.PlayerAimbot; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; + +/** + * AutoBow – draws, holds, aims and fires the bow once alignment is acceptable. + * Fixes implemented: + * - Removed premature slot switching while still aiming within viable tolerance. + * - Added hold grace (lostHoldTicks) with decay to avoid jitter-based cancellations. + * - Added configurable holdGraceTicks slider. + * - Respect switchSlotOnFail toggle before switching slots. + * - Proper tolerance scaling after long inactivity ( >60 / >120 ticks since last fire ). + * - Simplified and de-duplicated initialization logic. + */ +public class AutoBow extends TickModule { + public static final AutoBow INSTANCE = new AutoBow("murdermystery.autobow", "AutoBow", "Automatically aims and fires a bow at the murderer."); + + private final SliderWidget shootDelay = new SliderWidget("Shoot Delay", "Maximum delay (ticks) to fully draw before shooting").withBounds(20, 45, 60).withAccuracy(1); + private final SliderWidget targetRange = new SliderWidget("Max Range", "Maximum range to shoot a target.").withBounds(3, 5, 15); + private final ToggleWidget genericMode = new ToggleWidget("Generic Mode", "Use PlayerAimbot target without Murder Mystery prioritization").withDefaultState(false); + private final ToggleWidget prediction = new ToggleWidget("Prediction", "Use PlayerAimbot prediction settings while active").withDefaultState(false); + private final SliderWidget predictionTicks = new SliderWidget("Prediction Ticks", "Ticks ahead to predict").withBounds(0, 5, 20).withAccuracy(1); + private final SliderWidget holdAngleTolerance = new SliderWidget("Hold Angle", "Angle (deg) allowed while continuing to hold draw").withBounds(5, 25, 60).withAccuracy(1); + private final SliderWidget holdGraceTicks = new SliderWidget("Hold Grace", "Ticks outside hold tolerance before cancelling").withBounds(1, 3, 12).withAccuracy(0); + private final ToggleWidget switchSlotOnFail = new ToggleWidget("Slot Switch Fail", "Switch slot after failed shot alignment").withDefaultState(true); + + private boolean paOldEnableState; + private boolean paOldPredictionState; + private boolean paOldMMState; + private double paOldPredictionTicks; + private Boolean paMMOldState = false; + + private boolean isMurderer = false; + + private int drawTicks = 0; + private int requiredDrawTicks = 20; + private boolean drawing = false; + private int ticksSinceLastFire = 0; + private int lostHoldTicks = 0; + + private AutoBow(String id, String name, String tooltip) { + super(ToggleWidget::module, id, name, tooltip); + this.setChildren(shootDelay, targetRange, genericMode, prediction, predictionTicks, holdAngleTolerance, holdGraceTicks, switchSlotOnFail); + shootDelay.registerConfigKey(id + ".shootDelay"); + targetRange.registerConfigKey(id + ".targetRange"); + genericMode.registerConfigKey(id + ".genericMode"); + predictionTicks.registerConfigKey(id + ".predictionTicks"); + holdAngleTolerance.registerConfigKey(id + ".holdAngleTolerance"); + holdGraceTicks.registerConfigKey(id + ".holdGraceTicks"); + switchSlotOnFail.registerConfigKey(id + ".switchSlotOnFail"); + } + + @Override + public void onEnabledTick(MinecraftClient client, @NotNull ClientWorld world, @NotNull ClientPlayerEntity player) { + if (client.currentScreen != null) return; + + ticksSinceLastFire++; + + boolean shouldMM = !genericMode.getRawState(); + if (PlayerAimbot.INSTANCE.murderMysteryMode.getRawState() != shouldMM) { + PlayerAimbot.INSTANCE.murderMysteryMode.setRawState(shouldMM); + releaseIfDrawing(player); + } + + ensureActiveTarget(player); + LivingEntity target = PlayerAimbot.INSTANCE.activeTarget; + if (target != null) { + double maxRangeSq = square(targetRange.getRawState()); + if (!target.isAlive() || target.isRemoved() || player.squaredDistanceTo(target) > maxRangeSq) { + PlayerAimbot.INSTANCE.activeTarget = null; + target = null; + } + } + if (target == null) { + releaseIfDrawing(player); + return; + } + + if (!genericMode.getRawState()) { + LivingEntity fTarget = target; + Optional tPlayer = MurderMysteryAgent.getVisiblePlayers().stream().filter(p -> p.playerEntity == fTarget).findFirst(); + isMurderer = tPlayer.filter(pp -> pp.role == MurderMysteryAgent.PersistentPlayer.Role.MURDERER).isPresent(); + } + + selectBowIfNeeded(player); + if (!holdingBow(player)) { + releaseIfDrawing(player); + return; + } + + HitResult hr = client.crosshairTarget; + if (hr != null && hr.getType() == HitResult.Type.BLOCK) { + releaseIfDrawing(player); + return; + } + + if (!drawing) { + if (!canStartDrawing(player, target)) return; + startDrawing(player); + return; + } + + double baseTol = PlayerAimbot.INSTANCE.aimToleranceDeg.getRawState(); + double holdTol = holdAngleTolerance.getRawState(); + if (ticksSinceLastFire > 120) { + baseTol *= 1.6; + holdTol *= 1.25; + } else if (ticksSinceLastFire > 60) { + baseTol *= 1.4; + holdTol *= 1.15; + } + + double angleActual = computeAngle(player, target); + boolean predEnabled = prediction.getRawState(); + double predictedAngle = Double.NaN; + if (predEnabled) { + int pticks = Math.max(0, predictionTicks.getRawState().intValue()); + Vec3d predicted = target.getPos().add(target.getVelocity().multiply(pticks)) + .add(0, target.getStandingEyeHeight() * 0.5, 0); + predictedAngle = computeAngleToPoint(player, predicted); + } + + boolean withinHold = (!Double.isNaN(angleActual) && angleActual <= holdTol) || (predEnabled && !Double.isNaN(predictedAngle) && predictedAngle <= holdTol); + int graceLimit = Math.max(1, holdGraceTicks.getRawState().intValue()); + if (!withinHold) { + lostHoldTicks++; + if (lostHoldTicks >= graceLimit) { + failRelease(player); + return; + } + } else { + if (lostHoldTicks > 0) lostHoldTicks = Math.max(0, lostHoldTicks - 2); + } + + drawTicks++; + int minHoldTicks = 5; + if (drawTicks >= requiredDrawTicks && drawTicks >= minHoldTicks) { + boolean crosshairOn = client.crosshairTarget instanceof EntityHitResult ehr && ehr.getEntity() == target; + boolean actualOk = !Double.isNaN(angleActual) && angleActual <= baseTol; + boolean predictedOk = predEnabled && !Double.isNaN(predictedAngle) && predictedAngle <= baseTol; + if (crosshairOn || actualOk || predictedOk) { + fire(player); + } + } + } + + private boolean canStartDrawing(ClientPlayerEntity player, LivingEntity target) { + if (target == null) return false; + double maxRangeSq = square(targetRange.getRawState()); + if (player.squaredDistanceTo(target) > maxRangeSq) return false; + double baseTol = PlayerAimbot.INSTANCE.aimToleranceDeg.getRawState(); + double holdTol = holdAngleTolerance.getRawState(); + double angleActual = computeAngle(player, target); + if (!Double.isNaN(angleActual) && angleActual <= baseTol) return true; + boolean predEnabled = prediction.getRawState(); + if (predEnabled) { + int pticks = Math.max(0, predictionTicks.getRawState().intValue()); + Vec3d predicted = target.getPos().add(target.getVelocity().multiply(pticks)) + .add(0, target.getStandingEyeHeight() * 0.5, 0); + double pAng = computeAngleToPoint(player, predicted); + if (!Double.isNaN(pAng) && pAng <= baseTol) return true; + return !Double.isNaN(angleActual) && angleActual <= holdTol; + } + return !Double.isNaN(angleActual) && angleActual <= holdTol; + } + + private void ensureActiveTarget(ClientPlayerEntity player) { + LivingEntity current = PlayerAimbot.INSTANCE.activeTarget; + if (current == null || !current.isAlive() || current.isRemoved()) { + PlayerAimbot.INSTANCE.activeTarget = null; + if (drawing) failRelease(player); + try { + PlayerAimbot.INSTANCE.activeTarget = PlayerAimbot.getBestTargetFor(player); + } catch (Exception ignored) { + PlayerAimbot.INSTANCE.activeTarget = null; + } + } + } + + private void failRelease(ClientPlayerEntity player) { + if (switchSlotOnFail.getRawState()) { + releaseAndMaybeSwitch(player); + } else { + releaseIfDrawing(player); + } + } + + private void releaseAndMaybeSwitch(ClientPlayerEntity player) { + releaseIfDrawing(player); + switchHotbarSlot(player); + } + + private void selectBowIfNeeded(ClientPlayerEntity player) { + if (holdingBow(player)) return; + DefaultedList inv = player.getInventory().getMainStacks(); + for (int i = 0; i < inv.size(); i++) { + if (inv.get(i).isOf(Items.BOW)) { + player.getInventory().setSelectedSlot(i); + break; + } + } + } + + private boolean holdingBow(ClientPlayerEntity player) { + return player.getMainHandStack().isOf(Items.BOW) || player.getOffHandStack().isOf(Items.BOW); + } + + private double computeAngle(ClientPlayerEntity player, LivingEntity target) { + if (target == null) return Double.NaN; + Vec3d eye = player.getEyePos(); + Vec3d to = target.getPos().add(0, target.getStandingEyeHeight() * 0.5, 0).subtract(eye); + if (to.lengthSquared() == 0) return Double.NaN; + Vec3d look = player.getRotationVec(1.0F).normalize(); + Vec3d dir = to.normalize(); + double dot = Math.max(-1, Math.min(1, look.dotProduct(dir))); + return Math.toDegrees(Math.acos(dot)); + } + + private double computeAngleToPoint(ClientPlayerEntity player, Vec3d point) { + Vec3d eye = player.getEyePos(); + Vec3d to = point.subtract(eye); + if (to.lengthSquared() == 0) return 0.0; + Vec3d look = player.getRotationVec(1.0F).normalize(); + Vec3d dir = to.normalize(); + double dot = Math.max(-1, Math.min(1, look.dotProduct(dir))); + return Math.toDegrees(Math.acos(dot)); + } + + private void startDrawing(ClientPlayerEntity player) { + if (!holdingBow(player)) return; + if (player.isUsingItem()) return; + drawing = true; + drawTicks = 0; + lostHoldTicks = 0; + int max = shootDelay.getRawState().intValue(); + if (max < 20) max = 20; + requiredDrawTicks = 20 + (max > 20 ? ThreadLocalRandom.current().nextInt(Math.max(1, max - 19)) : 0); + if (MinecraftClient.getInstance().interactionManager != null) { + KeyBinding binding = KeyBinding.byId("key.use"); + if (binding != null) { + binding.setPressed(true); + KeyBindingAccessor accessor = (KeyBindingAccessor) binding; + accessor.setTimesPressed(accessor.getTimesPressed() + 1); + } + } + } + + private void fire(ClientPlayerEntity player) { + if (!drawing) return; + if (player.isUsingItem()) { + KeyBinding binding = KeyBinding.byId("key.use"); + if (binding != null) { + binding.setPressed(false); + KeyBindingAccessor accessor = (KeyBindingAccessor) binding; + accessor.setTimesPressed(Math.max(0, accessor.getTimesPressed() - 1)); + } + } + drawing = false; + drawTicks = 0; + lostHoldTicks = 0; + ticksSinceLastFire = 0; + } + + private void releaseIfDrawing(ClientPlayerEntity player) { + if (!drawing) return; + fire(player); + } + + private void switchHotbarSlot(ClientPlayerEntity player) { + if (player == null) return; + int current = player.getInventory().getSelectedSlot(); + for (int i = 1; i < 9; i++) { + int next = (current + i) % 9; + ItemStack stack = player.getInventory().getStack(next); + if (!stack.isEmpty() && !stack.isOf(Items.BOW)) { + player.getInventory().setSelectedSlot(next); + return; + } + } + player.getInventory().setSelectedSlot((current + 1) % 9); + } + + private double square(double v) { return v * v; } + + @Override + protected void whenEnabled() { + this.paOldEnableState = PlayerAimbot.INSTANCE.getRawState(); + this.paMMOldState = PlayerAimbot.INSTANCE.murderMysteryMode.getRawState(); + paOldPredictionState = PlayerAimbot.INSTANCE.prediction.getRawState(); + paOldPredictionTicks = PlayerAimbot.INSTANCE.predictionTicks.getRawState(); + paOldMMState = PlayerAimbot.INSTANCE.murderMysteryMode.getRawState(); + + PlayerAimbot.INSTANCE.widget.setRawState(true); + AutoClicker.INSTANCE.widget.setRawState(false); + PlayerAimbot.INSTANCE.predictionTicks.setRawState(predictionTicks.getRawState()); + PlayerAimbot.INSTANCE.murderMysteryMode.setRawState(!genericMode.getRawState()); + PlayerAimbot.INSTANCE.prediction.setRawState(prediction.getRawState()); + + isMurderer = false; + drawTicks = 0; + requiredDrawTicks = 20; + drawing = false; + lostHoldTicks = 0; + ticksSinceLastFire = 0; + } + + @Override + protected void whenDisabled() { + PlayerAimbot.INSTANCE.widget.setRawState(this.paOldEnableState); + PlayerAimbot.INSTANCE.murderMysteryMode.setRawState(this.paMMOldState); + PlayerAimbot.INSTANCE.prediction.setRawState(paOldPredictionState); + PlayerAimbot.INSTANCE.predictionTicks.setRawState(paOldPredictionTicks); + PlayerAimbot.INSTANCE.murderMysteryMode.setRawState(paOldMMState); + AutoClicker.INSTANCE.widget.setRawState(false); + + MinecraftClient mc = MinecraftClient.getInstance(); + if (mc.interactionManager != null && mc.player != null) { + mc.interactionManager.stopUsingItem(mc.player); + } + drawing = false; + drawTicks = 0; + requiredDrawTicks = 20; + lostHoldTicks = 0; + } +} diff --git a/src/main/java/dev/cigarette/module/zombies/Aimbot.java b/src/main/java/dev/cigarette/module/zombies/Aimbot.java index e5d2bded..68214c71 100644 --- a/src/main/java/dev/cigarette/module/zombies/Aimbot.java +++ b/src/main/java/dev/cigarette/module/zombies/Aimbot.java @@ -5,20 +5,16 @@ import dev.cigarette.gui.widget.SliderWidget; import dev.cigarette.gui.widget.ToggleWidget; import dev.cigarette.lib.PlayerEntityL; +import dev.cigarette.lib.AimingL; import dev.cigarette.lib.WeaponSelector; -import dev.cigarette.mixin.ClientWorldAccessor; import dev.cigarette.module.TickModule; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.client.network.PendingUpdateManager; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.world.ClientWorld; -import net.minecraft.entity.EntityPose; -import net.minecraft.network.packet.c2s.play.PlayerInteractItemC2SPacket; import net.minecraft.registry.tag.BlockTags; -import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.HitResult; import net.minecraft.util.math.Vec3d; @@ -76,18 +72,15 @@ protected void onEnabledTick(MinecraftClient client, @NotNull ClientWorld world, WeaponSelector.addCooldown(player.getInventory().getSelectedSlot()); - float aimYaw = (float) Math.toDegrees(Math.atan2(-vector.x, vector.z)); - float aimPitch = (float) Math.toDegrees(Math.asin(-vector.y)); + float[] angles = AimingL.anglesFromTo(player.getEyePos(), predictedPos); + float aimYaw = angles[0]; + float aimPitch = angles[1]; if (!silentAim.getRawState()) { PlayerEntityL.setRotationVector(player, vector); } - ClientWorldAccessor clientWorldAccessor = (ClientWorldAccessor) world; - try (PendingUpdateManager pendingUpdateManager = clientWorldAccessor.getPendingUpdateManager().incrementSequence()) { - int seq = pendingUpdateManager.getSequence(); - player.networkHandler.sendPacket(new PlayerInteractItemC2SPacket(Hand.MAIN_HAND, seq, aimYaw, aimPitch)); - } + AimingL.sendAimPacket(player, aimYaw, aimPitch); } } diff --git a/src/main/java/dev/cigarette/module/zombies/ReviveAura.java b/src/main/java/dev/cigarette/module/zombies/ReviveAura.java index 7ef23bba..893df53e 100644 --- a/src/main/java/dev/cigarette/module/zombies/ReviveAura.java +++ b/src/main/java/dev/cigarette/module/zombies/ReviveAura.java @@ -15,8 +15,8 @@ import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket; import net.minecraft.network.packet.c2s.play.PlayerInteractEntityC2SPacket; import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; -import net.minecraft.util.Hand; import net.minecraft.util.math.Vec3d; +import dev.cigarette.lib.AimingL; import org.jetbrains.annotations.NotNull; public class ReviveAura extends RenderModule { @@ -111,14 +111,12 @@ protected void onEnabledTick(MinecraftClient client, @NotNull ClientWorld world, } Vec3d targetCenterPos = target.getBoundingBox().getCenter(); - Vec3d direction = targetCenterPos.subtract(player.getEyePos()).normalize(); + float[] angles = AimingL.anglesFromTo(player.getEyePos(), targetCenterPos); + float aimYaw = angles[0]; + float aimPitch = angles[1]; - float aimYaw = (float) Math.toDegrees(Math.atan2(-direction.x, direction.z)); - float aimPitch = (float) Math.toDegrees(Math.asin(-direction.y)); - - player.networkHandler.sendPacket(new PlayerMoveC2SPacket.LookAndOnGround(aimYaw, aimPitch, player.isOnGround(), player.horizontalCollision)); - player.networkHandler.sendPacket(PlayerInteractEntityC2SPacket.attack(target, player.isSneaking())); - player.networkHandler.sendPacket(new HandSwingC2SPacket(Hand.MAIN_HAND)); + // Use centralized helper for look+attack+hand swing + AimingL.lookAndAttack(world, player, target, aimYaw, aimPitch); cooldownTicks += 25; } diff --git a/src/main/java/io/github/waqfs/gui/CigaretteScreen.java b/src/main/java/io/github/waqfs/gui/CigaretteScreen.java new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/io/github/waqfs/gui/hud/modules/ModuleListDisplay.java b/src/main/java/io/github/waqfs/gui/hud/modules/ModuleListDisplay.java new file mode 100644 index 00000000..e69de29b