diff --git a/src/main/java/com/example/addon/modules/Prediction.java b/src/main/java/com/example/addon/modules/Prediction.java index a2d4b3c..dc54da3 100644 --- a/src/main/java/com/example/addon/modules/Prediction.java +++ b/src/main/java/com/example/addon/modules/Prediction.java @@ -34,10 +34,8 @@ import net.minecraft.util.math.*; import net.minecraft.world.RaycastContext; import org.joml.Vector3d; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import javax.annotation.Nullable; +import java.util.*; public class Prediction extends Module { private final SettingGroup sgGeneral = settings.getDefaultGroup(); @@ -92,10 +90,38 @@ public class Prediction extends Module { .build() ); + public final Setting chargeOffset = sgGeneral.add(new IntSetting.Builder() + .name("charge-offset") + .description("What is the offset of the charge tick.") + .defaultValue(2) + .sliderMin(0) + .sliderMax(20) + .build() + ); + + public final Setting tickOffset = sgGeneral.add(new IntSetting.Builder() + .name("tick-offset") + .description("What is the offset of the target tick.") + .defaultValue(4) + .sliderMin(0) + .sliderMax(20) + .visible(() -> predictionLevel.get() >= 2) + .build() + ); + + public final Setting dynamicMultiplier = sgGeneral.add(new BoolSetting.Builder() + .name("dynamic-multiplier") + .description("Whether to dynamically correct the target Tick multiplier.") + .defaultValue(false) + .visible(() -> predictionLevel.get() >= 2) + .build() + ); + private final Setting allowHighThrows = sgGeneral.add(new BoolSetting.Builder() .name("allow-high-throws") .description("Whether or not to allow high throw prediction.") .defaultValue(false) + .visible(() -> predictionLevel.get() == 1) .build() ); @@ -176,19 +202,10 @@ public class Prediction extends Module { .build() ); - public final Setting projectTickOffset = sgSimulation.add(new IntSetting.Builder() - .name("project-tick-offset") - .description("What is the offset of the project tick.") - .defaultValue(2) - .sliderMin(0) - .sliderMax(20) - .build() - ); - public final Setting gravity = sgSimulation.add(new DoubleSetting.Builder() .name("gravity") .description("What is the acceleration of gravity on the projectile.") - .defaultValue(0.05000000074505806) + .defaultValue(0.05) .sliderMin(0.04) .sliderMax(0.06) .build() @@ -216,7 +233,7 @@ public class Prediction extends Module { .name("enable-probe") .description("Enable/disable block probes.") .defaultValue(false) - .visible(() -> predictionLevel.get() >= 1) + .visible(() -> (predictionLevel.get() == 1 && !allowHighThrows.get()) || predictionLevel.get() >= 2) .build() ); @@ -226,7 +243,7 @@ public class Prediction extends Module { .defaultValue(1) .range(0, 4) .sliderMax(4) - .visible(enableProbe::get) + .visible(() -> (enableProbe.isVisible() && enableProbe.get())) .build() ); @@ -236,7 +253,7 @@ public class Prediction extends Module { .defaultValue(0.5) .range(0, 1) .sliderMax(1) - .visible(enableProbe::get) + .visible(() -> (enableProbe.isVisible() && enableProbe.get())) .build() ); @@ -323,88 +340,57 @@ public class Prediction extends Module { @Override public void onDeactivate() { - targetEntity = null; + context = null; + renderData = null; } + private TargetContext context; + + private final PathData pathData = new PathData(); + @EventHandler private void onTick(TickEvent.Post event) { - clearPath(); - calculateTarget(); + renderData = null; + aimData = null; + + if (context != null && !isSelectableTarget(context.entity)) context = null; + + if (mc.options.attackKey.isPressed()) { + Entity entity = TargetUtils.get(this::isSelectableTarget, priority.get()); + if (entity != null) context = new TargetContext(entity); + } + + if (context == null) return; + + if (enableRender.get()) renderData = new RenderData(context); + + ItemStack itemStack = mc.player.getMainHandStack(); + if (!(itemStack.getItem() instanceof BowItem)) { + itemStack = mc.player.getOffHandStack(); + if (!(itemStack.getItem() instanceof BowItem)) itemStack = null; + } + + int chargeTick = itemStack != null ? mc.player.getItemUseTime() + chargeOffset.get() : -1; + + TargetResult targetResult = calculateTarget(context, chargeTick); + if (!targetResult.successed) return; + AngleResult angleResult = calculateAngle(targetResult.predicted, chargeTick); + if (!angleResult.successed) return; + PathResult pathResult = calculatePath(targetResult.predicted, angleResult.yaw, angleResult.pitch, chargeTick, true, pathData); + + if (enableRender.get()) renderData = new RenderData(context, targetResult, angleResult, pathResult, pathData); + if (aimAssist.get() && (!aimOnlyHit.get() || pathResult.hitTarget)) aimData = new AimData(angleResult.yaw, angleResult.pitch); } @EventHandler private void onRender(Render3DEvent event) { - if (aimAssist.get()) aim(event.tickDelta); - - if (!enableRender.get()) return; - - Vector3d lastPoint = null; - for (Vector3d point : points) { - if (lastPoint != null) { - event.renderer.line( - lastPoint.x, lastPoint.y, lastPoint.z, point.x, point.y, point.z, - isHitTarget ? hitLineColor.get() : missLineColor.get() - ); - } - lastPoint = point; - } - - if (hitQuad) { - if (hitQuadHorizontal) { - event.renderer.sideHorizontal( - hitQuad1.x, hitQuad1.y, hitQuad1.z, hitQuad1.x + 0.5, hitQuad1.z + 0.5, - isHitTarget ? hitSideColor.get() : missSideColor.get(), - isHitTarget ? hitLineColor.get() : missLineColor.get(), - shapeMode.get() - ); - } - else { - event.renderer.sideVertical( - hitQuad1.x, hitQuad1.y, hitQuad1.z, hitQuad2.x, hitQuad2.y, hitQuad2.z, - isHitTarget ? hitSideColor.get() : missSideColor.get(), - isHitTarget ? hitLineColor.get() : missLineColor.get(), - shapeMode.get() - ); - } - } - - if (targetEntity != null) { - if (targetCompleted) { - if (targetEntity.getBoundingBox().getCenter().distanceTo(targetBox.getCenter()) < 0.5) { - event.renderer.box( - targetEntity.getBoundingBox(), - isHitTarget ? hitSideColor.get() : missSideColor.get(), - isHitTarget ? hitLineColor.get() : missLineColor.get(), - shapeMode.get(), 0 - ); - } - else { - event.renderer.box( - targetEntity.getBoundingBox(), - targetSideColor.get(), targetLineColor.get(), - shapeMode.get(), 0 - ); - event.renderer.box( - targetBox, - isHitTarget ? hitSideColor.get() : missSideColor.get(), - isHitTarget ? hitLineColor.get() : missLineColor.get(), - shapeMode.get(), 0 - ); - } - } - else { - event.renderer.box( - targetEntity.getBoundingBox(), - targetSideColor.get(), targetLineColor.get(), - shapeMode.get(), 0 - ); - } - } + render(event); + aim(event.tickDelta); } @Override public String getInfoString() { - return EntityUtils.getName(targetEntity); + return EntityUtils.getName(context != null ? context.entity : null); } private boolean isSelectableTarget(Entity entity) { @@ -422,290 +408,132 @@ public class Prediction extends Module { return !(entity instanceof AnimalEntity) || babies.get() || !((AnimalEntity) entity).isBaby(); } - private double getCurrentCharge() { - ItemStack itemStack = mc.player.getMainHandStack(); - if (!(itemStack.getItem() instanceof BowItem)) { - itemStack = mc.player.getOffHandStack(); - if (!(itemStack.getItem() instanceof BowItem)) return 0.0; - } + private class TargetContext { + final Entity entity; + int entityTick; + Vector3d lastPosition; + int lastChargeTick; + int lastPredictedTick; + Vector3d lastPredictedPosition; + double multiplier; - return BowItem.getPullProgress(mc.player.getItemUseTime() + projectTickOffset.get()); - } + // For dynamic multiplier + final List predictedTick = new ArrayList(); + final List originalPosition = new ArrayList(); + final List predictedPosition = new ArrayList(); - // These variables are set by the calculateTarget() - private Entity targetEntity; - private Box targetBox; - private Vector3d targetLastPosition; - - // These variables are set by the calculateAngle() - private boolean targetCompleted; - private double targetYaw; - private double targetPitch; - private double targetCharge; - - // These variables are set by the calculatePath() - private final Pool vectorPool = new Pool<>(Vector3d::new); - private final List points = new ArrayList<>(); - private boolean hitQuad = false, hitQuadHorizontal = false; - private final Vector3d hitQuad1 = new Vector3d(); - private final Vector3d hitQuad2 = new Vector3d(); - private boolean isHitTarget; - private int hitTime; - - private void clearPath() { - for (Vector3d point : points) vectorPool.free(point); - points.clear(); - - hitQuad = false; - isHitTarget = false; - } - - private void calculatePath() { - clearPath(); - - double speed = targetCharge * 3; - - Vector3d lastPosition = new Vector3d(0.0, 0.0, 0.0); - Vector3d position = new Vector3d(0.0, 0.0, 0.0); - Vector3d velocity = new Vector3d(0.0, 0.0, 0.0); - position.set(mc.player.getX(), mc.player.getY(), mc.player.getZ()).add(0, mc.player.getEyeHeight(mc.player.getPose()) + eyeHeightOffset.get(), 0); - - double yaw = targetYaw; - double pitch = targetPitch; - - double x = -Math.sin(yaw * 0.017453292) * Math.cos(pitch * 0.017453292); - double y = -Math.sin(pitch * 0.017453292); - double z = Math.cos(yaw * 0.017453292) * Math.cos(pitch * 0.017453292); - - velocity.set(x, y, z).normalize().mul(speed); - velocity.add(mc.player.getVelocity().x, mc.player.isOnGround() ? 0.0D : mc.player.getVelocity().y, mc.player.getVelocity().z); - - HitResult hitResult = null; - Direction hitDirection = null; - - for (hitTime = 0; hitTime < (simulationSteps.get() > 0 ? simulationSteps.get() : Integer.MAX_VALUE) && !hitQuad && !isHitTarget; hitTime++) { - points.add(vectorPool.get().set(position)); - - lastPosition.set(position); - position.add(velocity); - - boolean isTouchingWater = false; - FluidState fluidState = mc.world.getFluidState(new BlockPos((int) position.x, (int) position.y, (int) position.z)); - if (fluidState.getFluid() == Fluids.WATER || fluidState.getFluid() == Fluids.FLOWING_WATER) - isTouchingWater = position.y - (int) position.y <= fluidState.getHeight(); - - velocity.mul(isTouchingWater ? waterDrag.get() : airDrag.get()); - velocity.sub(0, gravity.get(), 0); - - if (position.y < mc.world.getBottomY()) { - hitResult = MissHitResult.INSTANCE; - break; - } - - int chunkX = ChunkSectionPos.getSectionCoord(position.x); - int chunkZ = ChunkSectionPos.getSectionCoord(position.z); - if (!mc.world.getChunkManager().isChunkLoaded(chunkX, chunkZ)) { - hitResult = MissHitResult.INSTANCE; - break; - } - - hitResult = mc.world.raycast(new RaycastContext( - new Vec3d(lastPosition.x, lastPosition.y, lastPosition.z), new Vec3d(position.x, position.y, position.z), - RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player) - ); - if (hitResult.getType() != HitResult.Type.MISS) { - position = new Vector3d(hitResult.getPos().x, hitResult.getPos().y, hitResult.getPos().z); - hitQuad = true; - hitQuad1.set(position); - hitQuad2.set(position); - hitDirection = ((BlockHitResult) hitResult).getSide(); - } - - if (!targetBox.stretch(velocity.x, velocity.y, velocity.z).expand(4.0).contains(position.x, position.y, position.z)) - continue; - - Box extendedBox = targetBox; - - if (enableProbe.get()) { - hitResult = mc.world.raycast(new RaycastContext( - targetBox.getCenter(), targetBox.getCenter().add(-(targetBox.getLengthX() + EntityType.ARROW.getWidth()) / 2.0, 0, 0), - RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player) - ); - - double extendedMinX = hitResult.getType() != HitResult.Type.MISS ? hitResult.getPos().x : targetBox.minX - EntityType.ARROW.getWidth() / 2.0; - - hitResult = mc.world.raycast(new RaycastContext( - targetBox.getCenter(), targetBox.getCenter().add(0, -(targetBox.getLengthY() + EntityType.ARROW.getHeight()) / 2.0, 0), - RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player) - ); - - double extendedMinY = hitResult.getType() != HitResult.Type.MISS ? hitResult.getPos().y : targetBox.minY - EntityType.ARROW.getHeight() / 2.0; - - hitResult = mc.world.raycast(new RaycastContext( - targetBox.getCenter(), targetBox.getCenter().add(0, 0, -(targetBox.getLengthZ() + EntityType.ARROW.getWidth()) / 2.0), - RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player) - ); - - double extendedMinZ = hitResult.getType() != HitResult.Type.MISS ? hitResult.getPos().z : targetBox.minZ - EntityType.ARROW.getWidth() / 2.0; - - hitResult = mc.world.raycast(new RaycastContext( - targetBox.getCenter(), targetBox.getCenter().add((targetBox.getLengthX() + EntityType.ARROW.getWidth()) / 2.0, 0, 0), - RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player) - ); - - double extendedMaxX = hitResult.getType() != HitResult.Type.MISS ? hitResult.getPos().x : targetBox.maxX + EntityType.ARROW.getWidth() / 2.0; - - hitResult = mc.world.raycast(new RaycastContext( - targetBox.getCenter(), targetBox.getCenter().add(0, (targetBox.getLengthY() + EntityType.ARROW.getHeight()) / 2.0, 0), - RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player) - ); - - double extendedMaxY = hitResult.getType() != HitResult.Type.MISS ? hitResult.getPos().y : targetBox.maxY + EntityType.ARROW.getHeight() / 2.0; - - hitResult = mc.world.raycast(new RaycastContext( - targetBox.getCenter(), targetBox.getCenter().add(0, 0, (targetBox.getLengthZ() + EntityType.ARROW.getWidth()) / 2.0), - RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player) - ); - - double extendedMaxZ = hitResult.getType() != HitResult.Type.MISS ? hitResult.getPos().z : targetBox.maxZ + EntityType.ARROW.getWidth() / 2.0; - - extendedBox = new Box(extendedMinX, extendedMinY, extendedMinZ, extendedMaxX, extendedMaxY, extendedMaxZ); - } - - Optional optional = extendedBox.raycast(new Vec3d(lastPosition.x, lastPosition.y, lastPosition.z), new Vec3d(position.x, position.y, position.z)); - - if (optional.isPresent()) { - isHitTarget = true; - } - } - - if (hitQuad) { - if (hitDirection == Direction.UP || hitDirection == Direction.DOWN) { - hitQuadHorizontal = true; - hitQuad1.x -= 0.25; - hitQuad1.z -= 0.25; - hitQuad2.x += 0.25; - hitQuad2.z += 0.25; - } - else if (hitDirection == Direction.NORTH || hitDirection == Direction.SOUTH) { - hitQuadHorizontal = false; - hitQuad1.x -= 0.25; - hitQuad1.y -= 0.25; - hitQuad2.x += 0.25; - hitQuad2.y += 0.25; - } - else { - hitQuadHorizontal = false; - hitQuad1.z -= 0.25; - hitQuad1.y -= 0.25; - hitQuad2.z += 0.25; - hitQuad2.y += 0.25; - } - - points.add(Utils.set(vectorPool.get(), hitResult.getPos())); + TargetContext(Entity entity) { + this.entity = entity; + entityTick = 0; + lastPosition = new Vector3d(entity.getX(), entity.getY(), entity.getZ()); + lastChargeTick = -1; + lastPredictedPosition = new Vector3d(lastPosition); + multiplier = 1.0; } } - private void calculateTarget() { - if (mc.options.attackKey.isPressed() || !isSelectableTarget(targetEntity)) { - targetEntity = TargetUtils.get(this::isSelectableTarget, priority.get()); - if (targetEntity != null) targetLastPosition = new Vector3d(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ()); + private class TargetResult { + boolean successed; + Box predicted; + + TargetResult(boolean successed, Box predicted) { + this.successed = successed; + this.predicted = predicted; } + } - if (targetEntity == null) return; + private TargetResult calculateTarget(TargetContext context, int chargeTick) { + TargetResult result = new TargetResult(true, context.entity.getBoundingBox()); - targetBox = targetEntity.getBoundingBox(); - - calculateAngle(); + ++context.entityTick; if (predictionLevel.get() >= 2) { Box lastTargetBox = mc.player.getBoundingBox(); - for (int i = 0; simulationTargetSteps.get() > 0 ? i < simulationTargetSteps.get() : - targetBox.getCenter().distanceTo(lastTargetBox.getCenter()) > targetEpsilon.get(); i++) { - if (!targetCompleted) break; + if (chargeTick >= 3) { + int predictedTick = -1; - Vec3d velocity = new Vec3d( - targetEntity.getX() - targetLastPosition.x, - 0, - targetEntity.getZ() - targetLastPosition.z - ); + for (int i = 0; simulationTargetSteps.get() > 0 ? i < simulationTargetSteps.get() : + result.predicted.getCenter().distanceTo(lastTargetBox.getCenter()) > targetEpsilon.get(); i++) { - targetBox = targetEntity.getBoundingBox().offset(velocity.multiply(hitTime + projectTickOffset.get())); - calculateAngle(); + Vec3d velocity = new Vec3d( + context.entity.getBoundingBox().getCenter().x - context.lastPosition.x, + 0, + context.entity.getBoundingBox().getCenter().z - context.lastPosition.z + ); + + AngleResult angleResult = calculateAngle(result.predicted, chargeTick); + + if (!angleResult.successed) { + result.successed = false; + break; + } + + PathResult pathResult = calculatePath(result.predicted, angleResult.yaw, angleResult.pitch, chargeTick, false, null); + + predictedTick = pathResult.hitTick + tickOffset.get(); + result.predicted = context.entity.getBoundingBox().offset(velocity.multiply(predictedTick * context.multiplier)); + } + + context.lastPredictedTick = predictedTick; } + else result.successed = false; + + if (dynamicMultiplier.get()) { + if (context.lastChargeTick >= 3 && context.lastChargeTick > chargeTick && chargeTick != -1 && context.lastPredictedTick != -1) { + context.predictedTick.add(context.entityTick + context.lastPredictedTick); + context.originalPosition.add(new Vector3d(context.lastPosition)); + context.predictedPosition.add(new Vector3d(context.lastPredictedPosition)); + } + + if (!context.predictedTick.isEmpty() && context.predictedTick.getFirst() < context.entityTick) { + Vector3d originalPosition = context.originalPosition.getFirst(); + Vector3d actualPosition = new Vector3d(context.entity.getBoundingBox().getCenter().x, context.entity.getBoundingBox().getCenter().y, context.entity.getBoundingBox().getCenter().z); + Vector3d predictedPosition = context.predictedPosition.getFirst(); + + if (originalPosition.distance(actualPosition) > 0.25 && originalPosition.distance(predictedPosition) > 0.25) { + double modified = originalPosition.distance(actualPosition) / originalPosition.distance(predictedPosition) / context.multiplier; + if (!Double.isNaN(modified)) context.multiplier = MathHelper.clamp((modified + context.multiplier) / 2.0, 0.25, 1.25); + } + + context.predictedTick.removeFirst(); + context.originalPosition.removeFirst(); + context.predictedPosition.removeFirst(); + } + } + + context.lastPosition.set(context.entity.getBoundingBox().getCenter().x, context.entity.getBoundingBox().getCenter().y, context.entity.getBoundingBox().getCenter().z); + context.lastChargeTick = chargeTick; + context.lastPredictedPosition.set(result.predicted.getCenter().x, result.predicted.getCenter().y, result.predicted.getCenter().z); } - targetLastPosition = new Vector3d(targetEntity.getX(), targetEntity.getY(), targetEntity.getZ()); + return result; } - private double calculateHeightOffset(double pitch) { - double targetDistance = Math.sqrt( - (targetBox.getCenter().x - mc.player.getX()) * (targetBox.getCenter().x - mc.player.getX()) + - (targetBox.getCenter().z - mc.player.getZ()) * (targetBox.getCenter().z - mc.player.getZ()) - ); + private static class AngleResult { + boolean successed; + double yaw; + double pitch; - if (getCurrentCharge() <= 0) return Double.NaN; - double speed = getCurrentCharge() * 3; - - Vector3d lastPosition = new Vector3d(0.0, 0.0, 0.0); - Vector3d position = new Vector3d(0.0, 0.0, 0.0); - Vector3d velocity = new Vector3d(0.0, 0.0, 0.0); - position.set(mc.player.getX(), mc.player.getY(), mc.player.getZ()).add(0, mc.player.getEyeHeight(mc.player.getPose()) + eyeHeightOffset.get(), 0); - - double yaw = Rotations.getYaw(new Vec3d(targetBox.getCenter().x, targetBox.getCenter().y, targetBox.getCenter().z)); - - double x = -Math.sin(yaw * 0.017453292) * Math.cos(pitch * 0.017453292); - double y = -Math.sin(pitch * 0.017453292); - double z = Math.cos(yaw * 0.017453292) * Math.cos(pitch * 0.017453292); - - velocity.set(x, y, z).normalize().mul(speed); - velocity.add(mc.player.getVelocity().x, mc.player.isOnGround() ? 0.0D : mc.player.getVelocity().y, mc.player.getVelocity().z); - - for (int i = 0; i < (simulationSteps.get() > 0 ? simulationSteps.get() : Integer.MAX_VALUE); i++) { - lastPosition.set(position); - position.add(velocity); - - boolean isTouchingWater = false; - FluidState fluidState = mc.world.getFluidState(new BlockPos((int) position.x, (int) position.y, (int) position.z)); - if (fluidState.getFluid() == Fluids.WATER || fluidState.getFluid() == Fluids.FLOWING_WATER) - isTouchingWater = position.y - (int) position.y <= fluidState.getHeight(); - - velocity.mul(isTouchingWater ? waterDrag.get() : airDrag.get()); - velocity.sub(0, gravity.get(), 0); - - if (position.y < mc.world.getBottomY()) return Double.NEGATIVE_INFINITY; - - double laseDistance = Math.sqrt( - (lastPosition.x - mc.player.getX()) * (lastPosition.x - mc.player.getX()) + - (lastPosition.z - mc.player.getZ()) * (lastPosition.z - mc.player.getZ()) - ); - - double distance = Math.sqrt( - (position.x - mc.player.getX()) * (position.x - mc.player.getX()) + - (position.z - mc.player.getZ()) * (position.z - mc.player.getZ()) - ); - - if (distance > targetDistance) { - return MathHelper.lerp((targetDistance - laseDistance) / (distance - laseDistance), lastPosition.y, position.y) - targetBox.getCenter().y; - } + AngleResult(boolean successed, double yaw, double pitch) { + this.successed = successed; + this.yaw = yaw; + this.pitch = pitch; } - - return Double.NaN; } - private void calculateAngle() { - if (targetEntity == null) return; - - targetCompleted = false; + private AngleResult calculateAngle(Box target, int chargeTick) { + if (chargeTick < 3) return new AngleResult(false, Double.NaN, Double.NaN); // parabolic prediction if (predictionLevel.get() == 0) { - double posX = targetEntity.getPos().getX(); - double posY = targetEntity.getPos().getY(); - double posZ = targetEntity.getPos().getZ(); + if (chargeTick < 20) return new AngleResult(false, Double.NaN, Double.NaN); - posY -= 1.9f - targetEntity.getHeight(); + double posX = target.getCenter().getX(); + double posY = target.getCenter().getY(); + double posZ = target.getCenter().getZ(); + + posY -= 1.9f - target.getLengthY(); double relativeX = posX - mc.player.getX(); double relativeY = posY - mc.player.getY(); @@ -716,23 +544,18 @@ public class Prediction extends Module { float g = 0.006f; float pitch = (float) -Math.toDegrees(Math.atan((1.0 - Math.sqrt(1.0 - g * (g * hDistanceSq + 2 * relativeY))) / (g * hDistance))); - if (Float.isNaN(pitch)) { - targetYaw = Rotations.getYaw(targetEntity); - targetPitch = Rotations.getPitch(targetEntity); - } else { - targetYaw = Rotations.getYaw(targetEntity); - targetPitch = pitch; - } - - targetCharge = 1.0; - targetCompleted = true; - - calculatePath(); - return; + return new AngleResult( + true, + Rotations.getYaw(new Vec3d(target.getCenter().x, target.getCenter().y, target.getCenter().z)), + Float.isNaN(pitch) ? Rotations.getPitch(new Vec3d(target.getCenter().x, target.getCenter().y, target.getCenter().z)) : pitch + ); } + AngleResult result = new AngleResult(false, Double.NaN, Double.NaN); + + double yaw = Rotations.getYaw(new Vec3d(target.getCenter().x, target.getCenter().y, target.getCenter().z)); double highestPitch = 0.0; - boolean findPitch = false; + double lowPitch = highestPitch; // Basic physics prediction { @@ -743,69 +566,85 @@ public class Prediction extends Module { for (int i = 0; simulationAngleSteps.get() > 0 ? i < simulationAngleSteps.get() : maxPitch - minPitch > angleEpsilon.get(); i++) { double mid1 = minPitch + (maxPitch - minPitch) / 3.0; double mid2 = maxPitch - (maxPitch - minPitch) / 3.0; - double mid1Height = calculateHeightOffset(mid1); - double mid2Height = calculateHeightOffset(mid2); - if (Double.isNaN(mid1Height) || Double.isNaN(mid2Height)) return; + double mid1Height = calculatePath(target, yaw, mid1, chargeTick, false, null).heightOffset; + double mid2Height = calculatePath(target, yaw, mid2, chargeTick, false, null).heightOffset; + if (Double.isNaN(mid1Height) || Double.isNaN(mid2Height)) return new AngleResult(false, Double.NaN, Double.NaN); if (mid1Height < mid2Height) minPitch = mid1; else maxPitch = mid2; } highestPitch = (minPitch + maxPitch) / 2.0; - if (calculateHeightOffset(highestPitch) < 0.0) return; + if (calculatePath(target, yaw, highestPitch, chargeTick, false, null).heightOffset < 0.0) + return new AngleResult(false, Double.NaN, Double.NaN); } // Solve for the low pitch - double targetLowPitch = highestPitch; { double minPitch = highestPitch; double maxPitch = 90.0; for (int i = 0; simulationAngleSteps.get() > 0 ? i < simulationAngleSteps.get() : maxPitch - minPitch > angleEpsilon.get(); i++) { double mid = (minPitch + maxPitch) / 2.0; - double midHeight = calculateHeightOffset(mid); + double midHeight = calculatePath(target, yaw, mid, chargeTick, false, null).heightOffset; if (Double.isNaN(midHeight)) { - targetLowPitch = Double.NaN; + lowPitch = Double.NaN; break; } if (midHeight <= 0) maxPitch = mid; else minPitch = mid; } - if (!Double.isNaN(targetLowPitch)) targetLowPitch = (minPitch + maxPitch) / 2.0; + if (!Double.isNaN(lowPitch)) lowPitch = (minPitch + maxPitch) / 2.0; } - if (!Double.isNaN(targetLowPitch)) { - findPitch = true; + if (!Double.isNaN(lowPitch)) { + result = new AngleResult(true, yaw, lowPitch); + if (calculatePath(target, result.yaw, result.pitch, chargeTick, true, null).hitTarget) + return result; + } - targetPitch = targetLowPitch; - targetYaw = Rotations.getYaw(targetBox.getCenter()); - targetCharge = getCurrentCharge(); - targetCompleted = true; + // Solve for the high pitch if allowed + if (predictionLevel.get() == 1 && allowHighThrows.get()) { + double targetHighPitch = highestPitch; + double minPitch = -90.0; + double maxPitch = highestPitch; + for (int i = 0; simulationAngleSteps.get() > 0 ? i < simulationAngleSteps.get() : maxPitch - minPitch > angleEpsilon.get(); i++) { + double mid = (minPitch + maxPitch) / 2.0; + double midHeight = calculatePath(target, yaw, mid, chargeTick, false, null).heightOffset; + if (Double.isNaN(midHeight)) { + targetHighPitch = Double.NaN; + break; + } + if (midHeight <= 0) + minPitch = mid; + else maxPitch = mid; + } - calculatePath(); + return new AngleResult(!Double.isNaN(targetHighPitch), yaw, (minPitch + maxPitch) / 2.0); } } // If you can't hit because a block is blocking, try to use a probe. - if (targetCompleted && !isHitTarget && findPitch && enableProbe.get()) { + if (result.successed && enableProbe.get()) { int numProbe = (int) (probeRange.get() / probeInterval.get()); boolean[][] arrayProbe = new boolean[numProbe * 2 + 1][numProbe * 2 + 1]; double targetDistance = Math.sqrt( - (targetBox.getCenter().x - mc.player.getX()) * (targetBox.getCenter().x - mc.player.getX()) + - (targetBox.getCenter().z - mc.player.getZ()) * (targetBox.getCenter().z - mc.player.getZ()) + (target.getCenter().x - mc.player.getX()) * (target.getCenter().x - mc.player.getX()) + + (target.getCenter().z - mc.player.getZ()) * (target.getCenter().z - mc.player.getZ()) ); - double delta = Math.atan2(targetDistance, probeInterval.get()); - - double centerYaw = targetYaw; - double centerPitch = targetPitch; + double delta = Math.toDegrees(Math.atan2(probeInterval.get(), targetDistance)); for (int i = 0; i < numProbe * 2 + 1; ++i) { for (int j = 0; j < numProbe * 2 + 1; ++j) { - targetYaw = centerYaw + (i - numProbe) * delta; - targetPitch = centerPitch + (j - numProbe) * delta; - calculatePath(); - arrayProbe[i][j] = isHitTarget; + arrayProbe[i][j] = calculatePath( + target, + result.yaw + (i - numProbe) * delta, + result.pitch + (j - numProbe) * delta, + chargeTick, + true, + null + ).hitTarget; } } @@ -844,69 +683,303 @@ public class Prediction extends Module { } } - targetYaw = centerYaw; - targetPitch = centerPitch; - targetYaw += (maxI - numProbe) * delta; - targetPitch += (maxJ - numProbe) * delta; + result.yaw += (maxI - numProbe) * delta; + result.pitch += (maxJ - numProbe) * delta; - if (maxI != 0 && arrayProbe[maxI - 1][maxJ]) targetYaw -= delta / 2.0; - if (maxJ != 0 && arrayProbe[maxI][maxJ - 1]) targetPitch -= delta / 2.0; - if (maxI != numProbe * 2 && arrayProbe[maxI + 1][maxJ]) targetYaw += delta / 2.0; - if (maxJ != numProbe * 2 && arrayProbe[maxI][maxJ + 1]) targetPitch += delta / 2.0; - - calculatePath(); + if (maxI != 0 && arrayProbe[maxI - 1][maxJ]) result.yaw -= delta / 2.0; + if (maxJ != 0 && arrayProbe[maxI][maxJ - 1]) result.pitch -= delta / 2.0; + if (maxI != numProbe * 2 && arrayProbe[maxI + 1][maxJ]) result.yaw += delta / 2.0; + if (maxJ != numProbe * 2 && arrayProbe[maxI][maxJ + 1]) result.pitch += delta / 2.0; } - // Solve for the high pitch if allowed - if (!isHitTarget && allowHighThrows.get()) { - double targetHighPitch = highestPitch; - double minPitch = -90.0; - double maxPitch = highestPitch; - for (int i = 0; simulationAngleSteps.get() > 0 ? i < simulationAngleSteps.get() : maxPitch - minPitch > angleEpsilon.get(); i++) { - double mid = (minPitch + maxPitch) / 2.0; - double midHeight = calculateHeightOffset(mid); - if (Double.isNaN(midHeight)) { - targetHighPitch = Double.NaN; - break; - } - if (midHeight <= 0) - minPitch = mid; - else maxPitch = mid; - } + return result; + } - if (!Double.isNaN(targetHighPitch)) { - targetHighPitch = (minPitch + maxPitch) / 2.0; + private static class PathData { + final Pool vectorPool = new Pool<>(Vector3d::new); + final List points = new ArrayList<>(); + boolean hitQuad = false, hitQuadHorizontal = false; + final Vector3d hitQuad1 = new Vector3d(); + final Vector3d hitQuad2 = new Vector3d(); - targetPitch = targetHighPitch; - targetYaw = Rotations.getYaw(targetBox.getCenter()); - targetCharge = getCurrentCharge(); - targetCompleted = true; + void clear() { + for (Vector3d point : points) vectorPool.free(point); + points.clear(); - calculatePath(); - } + hitQuad = false; } } + private static class PathResult { + boolean hitTarget; + int hitTick; + double heightOffset; + + PathResult(boolean hitTarget, int hitTick, double heightOffset) { + this.hitTarget = hitTarget; + this.hitTick = hitTick; + this.heightOffset = heightOffset; + } + } + + private PathResult calculatePath(Box target, double yaw, double pitch, int chargeTick, boolean blockRaycast, @Nullable PathData pathData) { + if (pathData != null) pathData.clear(); + if (chargeTick < 3) return new PathResult(false, 0, Double.NaN); + + double charge = chargeTick * (chargeTick + 40.0 ) / 1200.0; + if (charge > 1.0) charge = 1.0; + + double speed = charge * 3.0; + + double targetDistance = Math.sqrt( + (target.getCenter().x - mc.player.getX()) * (target.getCenter().x - mc.player.getX()) + + (target.getCenter().z - mc.player.getZ()) * (target.getCenter().z - mc.player.getZ()) + ); + + Vector3d lastPosition = new Vector3d(0.0, 0.0, 0.0); + Vector3d position = new Vector3d(0.0, 0.0, 0.0); + Vector3d velocity = new Vector3d(0.0, 0.0, 0.0); + position.set(mc.player.getX(), mc.player.getY(), mc.player.getZ()).add(0, mc.player.getEyeHeight(mc.player.getPose()) + eyeHeightOffset.get(), 0); + + double x = -Math.sin(yaw * 0.017453292) * Math.cos(pitch * 0.017453292); + double y = -Math.sin(pitch * 0.017453292); + double z = Math.cos(yaw * 0.017453292) * Math.cos(pitch * 0.017453292); + + velocity.set(x, y, z).normalize().mul(speed); + velocity.add(mc.player.getVelocity().x, mc.player.isOnGround() ? 0.0D : mc.player.getVelocity().y, mc.player.getVelocity().z); + + PathResult result = new PathResult(false, 0, Double.NaN); + + for (int i = 0; i < (simulationSteps.get() > 0 ? simulationSteps.get() : Integer.MAX_VALUE); i++) { + if (pathData != null) pathData.points.add(pathData.vectorPool.get().set(position)); + + lastPosition.set(position); + position.add(velocity); + + boolean isTouchingWater = false; + FluidState fluidState = mc.world.getFluidState(new BlockPos((int) position.x, (int) position.y, (int) position.z)); + if (fluidState.getFluid() == Fluids.WATER || fluidState.getFluid() == Fluids.FLOWING_WATER) + isTouchingWater = position.y - (int) position.y <= fluidState.getHeight(); + + velocity.mul(isTouchingWater ? waterDrag.get() : airDrag.get()); + velocity.sub(0, gravity.get(), 0); + + if (position.y < mc.world.getBottomY()) { + if (Double.isNaN(result.heightOffset)) result.heightOffset = Double.NEGATIVE_INFINITY; + break; + } + + int chunkX = ChunkSectionPos.getSectionCoord(position.x); + int chunkZ = ChunkSectionPos.getSectionCoord(position.z); + if (!mc.world.getChunkManager().isChunkLoaded(chunkX, chunkZ)) break; + + if (blockRaycast) { + HitResult hitResult = mc.world.raycast(new RaycastContext( + new Vec3d(lastPosition.x, lastPosition.y, lastPosition.z), new Vec3d(position.x, position.y, position.z), + RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player) + ); + if (hitResult.getType() != HitResult.Type.MISS) { + if (pathData != null) { + position = new Vector3d(hitResult.getPos().x, hitResult.getPos().y, hitResult.getPos().z); + Direction hitDirection = ((BlockHitResult) hitResult).getSide(); + + pathData.hitQuad = true; + pathData.hitQuad1.set(position); + pathData.hitQuad2.set(position); + + if (hitDirection == Direction.UP || hitDirection == Direction.DOWN) { + pathData.hitQuadHorizontal = true; + pathData.hitQuad1.x -= 0.25; + pathData.hitQuad1.z -= 0.25; + pathData.hitQuad2.x += 0.25; + pathData.hitQuad2.z += 0.25; + } + else if (hitDirection == Direction.NORTH || hitDirection == Direction.SOUTH) { + pathData.hitQuadHorizontal = false; + pathData.hitQuad1.x -= 0.25; + pathData.hitQuad1.y -= 0.25; + pathData.hitQuad2.x += 0.25; + pathData.hitQuad2.y += 0.25; + } + else { + pathData.hitQuadHorizontal = false; + pathData.hitQuad1.z -= 0.25; + pathData.hitQuad1.y -= 0.25; + pathData.hitQuad2.z += 0.25; + pathData.hitQuad2.y += 0.25; + } + + pathData.points.add(Utils.set(pathData.vectorPool.get(), hitResult.getPos())); + } + return result; + } + } + + if (!result.hitTarget) { + Box extendedBox = target.expand(0.3); + + Optional optional = extendedBox.raycast(new Vec3d(lastPosition.x, lastPosition.y, lastPosition.z), new Vec3d(position.x, position.y, position.z)); + + if (optional.isPresent()) { + result.hitTarget = true; + result.hitTick = i; + } + } + + if (Double.isNaN(result.heightOffset)) { + double laseDistance = Math.sqrt( + (lastPosition.x - mc.player.getX()) * (lastPosition.x - mc.player.getX()) + + (lastPosition.z - mc.player.getZ()) * (lastPosition.z - mc.player.getZ()) + ); + + double distance = Math.sqrt( + (position.x - mc.player.getX()) * (position.x - mc.player.getX()) + + (position.z - mc.player.getZ()) * (position.z - mc.player.getZ()) + ); + + if (distance > targetDistance) { + result.heightOffset = MathHelper.lerp( + (targetDistance - laseDistance) / (distance - laseDistance), lastPosition.y, position.y + ) - target.getCenter().y; + } + } + + if (result.hitTarget && !Double.isNaN(result.heightOffset)) break; + } + + return result; + } + + private class RenderData { + final TargetContext context; + + final boolean renderPath; + + final TargetResult targetResult; + final AngleResult angleResult; + final PathResult pathResult; + final PathData pathData; + + RenderData(TargetContext context) { + this.context = context; + + renderPath = false; + + this.targetResult = null; + this.angleResult = null; + this.pathResult = null; + this.pathData = null; + } + + RenderData(TargetContext context, TargetResult targetResult, AngleResult angleResult, PathResult pathResult, PathData pathData) { + this.context = context; + + renderPath = true; + + this.targetResult = targetResult; + this.angleResult = angleResult; + this.pathResult = pathResult; + this.pathData = pathData; + } + } + + private RenderData renderData; + + private void render(Render3DEvent event) { + if (renderData == null) return; + + if (!renderData.renderPath) { + event.renderer.box( + renderData.context.entity.getBoundingBox(), + targetSideColor.get(), targetLineColor.get(), + shapeMode.get(), 0 + ); + return; + } + + Vector3d lastPoint = null; + for (Vector3d point : renderData.pathData.points) { + if (lastPoint != null) { + event.renderer.line( + lastPoint.x, lastPoint.y, lastPoint.z, point.x, point.y, point.z, + renderData.pathResult.hitTarget ? hitLineColor.get() : missLineColor.get() + ); + } + lastPoint = point; + } + + if (renderData.pathData.hitQuad) { + if (renderData.pathData.hitQuadHorizontal) { + event.renderer.sideHorizontal( + renderData.pathData.hitQuad1.x, renderData.pathData.hitQuad1.y, renderData.pathData.hitQuad1.z, renderData.pathData.hitQuad1.x + 0.5, renderData.pathData.hitQuad1.z + 0.5, + renderData.pathResult.hitTarget ? hitSideColor.get() : missSideColor.get(), + renderData.pathResult.hitTarget ? hitLineColor.get() : missLineColor.get(), + shapeMode.get() + ); + } + else { + event.renderer.sideVertical( + renderData.pathData.hitQuad1.x, renderData.pathData.hitQuad1.y, renderData.pathData.hitQuad1.z, renderData.pathData.hitQuad2.x, renderData.pathData.hitQuad2.y, renderData.pathData.hitQuad2.z, + renderData.pathResult.hitTarget ? hitSideColor.get() : missSideColor.get(), + renderData.pathResult.hitTarget ? hitLineColor.get() : missLineColor.get(), + shapeMode.get() + ); + } + } + + if (renderData.context.entity.getBoundingBox().getCenter().distanceTo(renderData.targetResult.predicted.getCenter()) < 0.5) { + event.renderer.box( + renderData.context.entity.getBoundingBox(), + renderData.pathResult.hitTarget ? hitSideColor.get() : missSideColor.get(), + renderData.pathResult.hitTarget ? hitLineColor.get() : missLineColor.get(), + shapeMode.get(), 0 + ); + } + else { + event.renderer.box( + renderData.context.entity.getBoundingBox(), + targetSideColor.get(), targetLineColor.get(), + shapeMode.get(), 0 + ); + event.renderer.box( + renderData.targetResult.predicted, + renderData.pathResult.hitTarget ? hitSideColor.get() : missSideColor.get(), + renderData.pathResult.hitTarget ? hitLineColor.get() : missLineColor.get(), + shapeMode.get(), 0 + ); + } + } + + private class AimData { + double yaw; + double pitch; + + AimData(double yaw, double pitch) { + this.yaw = yaw; + this.pitch = pitch; + } + } + + private AimData aimData; + private void aim(double tickDelta) { - if (targetEntity == null) return; - if (!targetCompleted) return; - if (!isHitTarget && aimOnlyHit.get()) return; + if (aimData == null) return; if (!mc.options.useKey.isPressed()) return; if (!InvUtils.testInHands(Items.BOW)) return; if (instant.get()) { - mc.player.setYaw((float) targetYaw); + mc.player.setYaw((float) aimData.yaw); } else { - double deltaAngle = MathHelper.wrapDegrees(targetYaw - mc.player.getYaw()); + double deltaAngle = MathHelper.wrapDegrees(aimData.yaw - mc.player.getYaw()); double toRotate = speed.get() * (deltaAngle >= 0 ? 1 : -1) * tickDelta; if ((toRotate >= 0 && toRotate > deltaAngle) || (toRotate < 0 && toRotate < deltaAngle)) toRotate = deltaAngle; mc.player.setYaw(mc.player.getYaw() + (float) toRotate); } if (instant.get()) { - mc.player.setPitch((float) targetPitch); + mc.player.setPitch((float) aimData.pitch); } else { - double deltaAngle = MathHelper.wrapDegrees(targetPitch - mc.player.getPitch()); + double deltaAngle = MathHelper.wrapDegrees(aimData.pitch - mc.player.getPitch()); double toRotate = speed.get() * (deltaAngle >= 0 ? 1 : -1) * tickDelta; if ((toRotate >= 0 && toRotate > deltaAngle) || (toRotate < 0 && toRotate < deltaAngle)) toRotate = deltaAngle; mc.player.setPitch(mc.player.getPitch() + (float) toRotate);