package com.example.client.autohit;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.monster.Monster;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.SwordItem;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
public class AutoHit {
public static final AutoHit INSTANCE = new AutoHit();
public final AutoHitSettings settings = new AutoHitSettings();
private final Random rng = new Random();
private long nextHitTime = 0;
private boolean attackedThisJump = false;
private long fallStartTime = -1;
private boolean wasFalling = false;
// Snap
private float snapYaw = 0f;
private float snapPitch = 0f;
private boolean snapActive = false;
private boolean returning = false;
public void tick(Minecraft mc) {
if (mc.player == null || mc.level == null || mc.getWindow() == null) return;
LocalPlayer player = mc.player;
// 1. Управление hitboxExpand (клавиши K/L)
long window = mc.getWindow().getWindow();
if (org.lwjgl.glfw.GLFW.glfwGetKey(window, org.lwjgl.glfw.GLFW.GLFW_KEY_L) == 1) {
settings.hitboxExpand = Math.min(1.0f, settings.hitboxExpand + 0.005f);
player.displayClientMessage(net.minecraft.network.chat.Component.literal(
"§7Expand: §b" + String.format("%.2f", settings.hitboxExpand)), true);
}
if (org.lwjgl.glfw.GLFW.glfwGetKey(window, org.lwjgl.glfw.GLFW.GLFW_KEY_K) == 1) {
settings.hitboxExpand = 0.0f;
player.displayClientMessage(net.minecraft.network.chat.Component.literal("§cHitbox Reset"), true);
}
// 2. Snap Aim
if (settings.hitboxEnabled) {
tickSnap(mc, player);
} else {
snapActive = false;
returning = false;
}
// 3. Основные проверки AutoHit
if (!settings.enabled) return;
if (settings.noGui && mc.screen != null) return;
if (settings.noEating && player.isUsingItem()) return;
if (settings.swordOnly && !(player.getMainHandItem().getItem() instanceof SwordItem)) return;
// --- ЛОГИКА ИЗ EXPENSIVE (ОБХОД АНТИЧИТОВ) ---
// Проверка кулдауна удара (если меньше 93%, не бьем)
if (player.getAttackCooldownProgress(0.5f) < 0.93f) return;
// Проверяем условия, когда крит невозможен физически
boolean isBadCondition = player.isInWater() || player.onClimbable() || player.isPassenger();
if (settings.crits && !isBadCondition) {
// Если мы на земле или летим вверх - ждем момента падения
if (player.onGround() || player.getDeltaMovement().y >= 0) return;
}
// 4. Поиск и атака цели
LivingEntity target = findTarget(mc, player);
if (target != null && target.isAlive()) {
// ПОВОРОТ: Silent Rotation перед ударом
float[] rot = getRotations(player, target, settings.getExpandFor(target.getId()));
player.connection.send(new net.minecraft.network.protocol.game.ServerboundMovePlayerPacket.Rot(
rot[0], rot[1], player.onGround(), player.horizontalCollision));
// УДАР: Используем interactionManager (как в Expensive)
if (mc.gameMode != null) {
mc.gameMode.attack(player, target);
}
// ЗАМАХ: Синхронный замах
player.swing(net.minecraft.world.InteractionHand.MAIN_HAND);
// Небольшая задержка после удара, чтобы пакеты не слипались
nextHitTime = System.currentTimeMillis() + 25;
}
}
private void tickSnap(Minecraft mc, LocalPlayer player) {
LivingEntity target = findSnapTarget(mc, player);
if (target != null) {
float[] targetRot = getRotationsWithRandom(player, target);
if (!snapActive) {
snapYaw = targetRot[0];
snapPitch = targetRot[1];
snapActive = true;
returning = false;
}
snapYaw = lerpAngle(snapYaw, targetRot[0], 0.15f);
snapPitch = lerpAngle(snapPitch, targetRot[1], 0.25f);
// Только голова и тело — НЕ трогаем yRot (движение) и xRot (камера)
player.setYHeadRot(snapYaw);
player.setYBodyRot(snapYaw);
player.connection.send(new net.minecraft.network.protocol.game.ServerboundMovePlayerPacket.Rot(
snapYaw, snapPitch, player.onGround(), player.horizontalCollision));
// Корректируем движение, чтобы "идти вперед" означало "идти к цели"
correctMovement(player, snapYaw);
} else {
if (snapActive) {
snapActive = false;
returning = true;
}
if (returning) {
float camYaw = player.getYRot();
float camPitch = player.getXRot();
snapYaw = lerpAngle(snapYaw, camYaw, 0.2f);
snapPitch = lerpAngle(snapPitch, camPitch, 0.2f);
player.setYHeadRot(snapYaw);
player.setYBodyRot(snapYaw);
player.connection.send(new net.minecraft.network.protocol.game.ServerboundMovePlayerPacket.Rot(
snapYaw, snapPitch, player.onGround(), player.horizontalCollision));
float diffY = snapYaw - camYaw;
while (diffY > 180f) diffY -= 360f;
while (diffY < -180f) diffY += 360f;
if (Math.abs(diffY) < 0.5f && Math.abs(snapPitch - camPitch) < 0.5f) {
returning = false;
player.connection.send(new net.minecraft.network.protocol.game.ServerboundMovePlayerPacket.Rot(
camYaw, camPitch, player.onGround(), player.horizontalCollision));
}
}
}
}
private LivingEntity findSnapTarget(Minecraft mc, LocalPlayer player) {
Vec3 eyePos = player.getEyePosition(1.0f);
double range = settings.range + settings.hitboxExpand + 1.0;
AABB searchBox = new AABB(
player.getX() - range, player.getY() - range, player.getZ() - range,
player.getX() + range, player.getY() + range, player.getZ() + range);
List<LivingEntity> entities = mc.level.getEntitiesOfClass(
LivingEntity.class, searchBox,
e -> e != player && e.isAlive() && isValidSnapTarget(e));
if (entities.isEmpty()) return null;
org.joml.Vector3f lv = mc.gameRenderer.getMainCamera().getLookVector();
Vec3 rayEnd = eyePos.add(lv.x * range, lv.y * range, lv.z * range);
return entities.stream()
.filter(e -> {
AABB box = e.getBoundingBox().inflate(
(double) settings.hitboxExpand, 0.0, (double) settings.hitboxExpand);
return box.clip(eyePos, rayEnd).isPresent();
})
.min(Comparator.comparingDouble(e -> eyePos.distanceTo(e.getBoundingBox().getCenter())))
.orElse(null);
}
private LivingEntity findTarget(Minecraft mc, LocalPlayer player) {
Vec3 eyePos = player.getEyePosition(1.0f);
double range = settings.range;
AABB searchBox = player.getBoundingBox().inflate(range + settings.hitboxExpand);
List<LivingEntity> entities = mc.level.getEntitiesOfClass(
LivingEntity.class, searchBox,
e -> e != player && e.isAlive() && isValidTarget(e));
return entities.stream()
.filter(e -> {
float expand = settings.getExpandFor(e.getId());
AABB box = e.getBoundingBox().inflate(expand, 0.0, expand);
return box.distanceToSqr(eyePos) <= range * range;
})
.min(Comparator.comparingDouble(e -> eyePos.distanceTo(e.getBoundingBox().getCenter())))
.orElse(null);
}
private boolean isValidTarget(LivingEntity e) {
return switch (settings.targetMode) {
case PLAYERS -> e instanceof Player;
case MOBS -> e instanceof Monster || e instanceof Animal;
case ALL -> true;
};
}
private boolean isValidSnapTarget(LivingEntity e) {
return switch (settings.snapTargetMode) {
case PLAYERS -> e instanceof Player;
case MOBS -> e instanceof Monster || e instanceof Animal;
case ALL -> true;
};
}
private float[] getRotations(LocalPlayer player, LivingEntity target, float expand) {
Vec3 eyePos = player.getEyePosition(1.0f);
// Рандомим точку удара по высоте от 0.3 до 0.7 хитбокса
double randomYMult = 0.3 + (rng.nextDouble() * 0.4);
double targetY = target.getY() + (target.getBbHeight() * randomYMult);
double diffX = target.getX() - eyePos.x;
double diffZ = target.getZ() - eyePos.z;
double diffY = targetY - eyePos.y;
double dist = Math.sqrt(diffX * diffX + diffZ * diffZ);
float yaw = (float)(Math.atan2(diffZ, diffX) * 180.0 / Math.PI) - 90.0f;
float pitch = (float)-(Math.atan2(diffY, dist) * 180.0 / Math.PI);
return new float[]{yaw, pitch};
}
private float[] getRotationsWithRandom(LocalPlayer player, LivingEntity target) {
Vec3 eyePos = player.getEyePosition(1.0f);
AABB box = target.getBoundingBox().inflate(
(double) settings.hitboxExpand, 0.0, (double) settings.hitboxExpand);
// X/Z всегда центр моба, рандомизация только по Y (от ног до головы)
double aimX = target.getX();
double aimZ = target.getZ();
double bellyY = box.minY + (box.maxY - box.minY) * 0.65;
double aimY = (settings.snapRandom > 0)
? box.minY + rng.nextDouble() * (box.maxY - box.minY)
: bellyY;
double diffX = aimX - eyePos.x;
double diffZ = aimZ - eyePos.z;
double diffY = aimY - eyePos.y;
double dist = Math.sqrt(diffX * diffX + diffZ * diffZ);
float yaw = (float)(Math.atan2(diffZ, diffX) * 180.0 / Math.PI) - 90.0f;
float pitch = (float)-(Math.atan2(diffY, dist) * 180.0 / Math.PI);
return new float[]{yaw, pitch};
}
private float lerpAngle(float current, float target, float speed) {
float diff = target - current;
while (diff > 180f) diff -= 360f;
while (diff < -180f) diff += 360f;
return current + diff * speed;
}
public boolean isSnapActive() { return snapActive; }
public float getSnapYaw() { return snapYaw; }
public float getSnapPitch() { return snapPitch; }
private void correctMovement(LocalPlayer player, float targetYaw) {
float moveForward = player.input.forwardImpulse;
float moveStrafe = player.input.leftImpulse;
// Разница между тем, куда смотрит камера, и целью
float yawDiff = (targetYaw - player.getYRot()) * ((float) Math.PI / 180f);
// Пересчитываем векторы движения
float cos = (float) Math.cos(yawDiff);
float sin = (float) Math.sin(yawDiff);
player.input.forwardImpulse = moveForward * cos + moveStrafe * sin;
player.input.leftImpulse = moveStrafe * cos - moveForward * sin;
}
}