From fd19d4bf630748e766ae14d9f6d8005a69366f96 Mon Sep 17 00:00:00 2001 From: Redstone1024 <2824517378@qq.com> Date: Tue, 27 Aug 2024 21:58:41 +0800 Subject: [PATCH] Add prediction based on physics simulations --- .../com/example/addon/modules/Prediction.java | 312 ++++++++++++++---- 1 file changed, 247 insertions(+), 65 deletions(-) diff --git a/src/main/java/com/example/addon/modules/Prediction.java b/src/main/java/com/example/addon/modules/Prediction.java index 53e9005..b53cf31 100644 --- a/src/main/java/com/example/addon/modules/Prediction.java +++ b/src/main/java/com/example/addon/modules/Prediction.java @@ -27,7 +27,6 @@ import net.minecraft.entity.projectile.ProjectileUtil; import net.minecraft.fluid.FluidState; import net.minecraft.fluid.Fluids; import net.minecraft.item.BowItem; -import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.util.hit.BlockHitResult; @@ -47,12 +46,45 @@ public class Prediction extends Module { private final SettingGroup sgSpeed = settings.createGroup("Aim Speed"); private final SettingGroup sgRender = settings.createGroup("Render"); - private final Setting predictionLevel = sgGeneral.add(new DoubleSetting.Builder() + private final Setting predictionLevel = sgGeneral.add(new IntSetting.Builder() .name("prediction-level") .description("The intelligence level for entity position prediction.") .defaultValue(0) - .range(0, 0) - .sliderMax(0) + .range(0, 1) + .sliderMax(1) + .build() + ); + + public final Setting simulationSteps = sgGeneral.add(new IntSetting.Builder() + .name("simulation-steps") + .description("How many steps to simulate projectiles. Zero for no limit.") + .defaultValue(250) + .sliderMax(5000) + .build() + ); + + public final Setting iterationSteps = sgGeneral.add(new IntSetting.Builder() + .name("iteration-steps") + .description("How many iterations to aim projectiles. Zero for no limit.") + .defaultValue(20) + .sliderMax(100) + .visible(() -> predictionLevel.get() >= 1) + .build() + ); + + public final Setting iterationEpsilon = sgGeneral.add(new DoubleSetting.Builder() + .name("iteration-epsilon") + .description("How much accuracy is needed to aim a projectile.") + .defaultValue(0.5) + .sliderMax(1) + .visible(() -> iterationSteps.get() == 0) + .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) .build() ); @@ -63,6 +95,14 @@ public class Prediction extends Module { .build() ); + private final Setting aimOnlyHit = sgGeneral.add(new BoolSetting.Builder() + .name("aim-only-hit") + .description("Whether to aim only when there is a chance of a hit.") + .defaultValue(true) + .visible(aimAssist::get) + .build() + ); + private final Setting range = sgTarget.add(new DoubleSetting.Builder() .name("range") .description("The maximum range the entity can be to aim at it.") @@ -108,14 +148,6 @@ public class Prediction extends Module { .build() ); - public final Setting simulationSteps = sgTarget.add(new IntSetting.Builder() - .name("simulation-steps") - .description("How many steps to simulate projectiles. Zero for no limit") - .defaultValue(500) - .sliderMax(5000) - .build() - ); - private final Setting instant = sgSpeed.add(new BoolSetting.Builder() .name("instant-look") .description("Instantly looks at the entity.") @@ -184,24 +216,25 @@ public class Prediction extends Module { @Override public void onDeactivate() { targetEntity = null; - isHitTarget = false; } @EventHandler - private void onTick(TickEvent.Post event) { } + private void onTick(TickEvent.Post event) { + if (mc.options.attackKey.isPressed() || !isSelectableTarget(targetEntity)) { + targetEntity = TargetUtils.get(this::isSelectableTarget, priority.get()); + } + + clearPath(); + if (targetEntity != null) calculateAngle(); + } @EventHandler private void onRender(Render3DEvent event) { float tickDelta = mc.world.getTickManager().isFrozen() ? 1 : event.tickDelta; - if (mc.options.attackKey.isPressed() || !isSelectableTarget(targetEntity)) { - targetEntity = TargetUtils.get(this::isSelectableTarget, priority.get()); - isHitTarget = false; - } + boolean canAim = aimAssist.get() && (isHitTarget || !aimOnlyHit.get()) && mc.options.useKey.isPressed() && InvUtils.testInHands(Items.BOW); - calculatePath(tickDelta); - if (aimAssist.get()) calculateAngle(tickDelta); - if (aimAssist.get() && mc.options.useKey.isPressed() && InvUtils.testInHands(Items.BOW)) aim(event.tickDelta); + if (canAim) aim(event.tickDelta); if (enableRender.get()) renderPath(event); } @@ -225,18 +258,40 @@ public class Prediction extends Module { return !(entity instanceof AnimalEntity) || babies.get() || !((AnimalEntity) entity).isBaby(); } - private Entity targetEntity; - private boolean isHitTarget; + 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; + } + return BowItem.getPullProgress(mc.player.getItemUseTime()); + } + + private static final double gravity = 0.05000000074505806; + private static final double airDrag = 0.99; + private static final double waterDrag = 0.6; + + // These variables are set by the onTick() + private Entity targetEntity; + + // These variables are set by the calculateAngle() private double targetYaw; private double targetPitch; + private double targetCharge; + // These variables are set by the calculatePath() for prediction level >= 1 + private double targetHighPitch; + private double targetLowPitch; + + // 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 Entity collidingEntity = null; + private boolean isHitTarget; private void clearPath() { for (Vector3d point : points) vectorPool.free(point); @@ -244,40 +299,28 @@ public class Prediction extends Module { hitQuad = false; collidingEntity = null; + isHitTarget = false; } - private void calculatePath(double tickDelta) { + private void calculatePath() { clearPath(); - isHitTarget = false; - ItemStack itemStack = mc.player.getMainHandStack(); - if (!(itemStack.getItem() instanceof BowItem)) { - itemStack = mc.player.getOffHandStack(); - if (!(itemStack.getItem() instanceof BowItem)) return; - } - - Item item = itemStack.getItem(); - double charge = BowItem.getPullProgress(mc.player.getItemUseTime()); - if (charge <= 0) return; - - double speed = charge * 3; - double gravity = 0.05000000074505806; - double airDrag = 0.99; - double waterDrag = 0.6; + 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); - Utils.set(position, mc.player, tickDelta).add(0, mc.player.getEyeHeight(mc.player.getPose()), 0); + position.set(mc.player.getX(), mc.player.getY(), mc.player.getZ()).add(0, mc.player.getEyeHeight(mc.player.getPose()), 0); - double yaw = MathHelper.lerp(tickDelta, mc.player.prevYaw, mc.player.getYaw()); - double pitch = MathHelper.lerp(tickDelta, mc.player.prevPitch, mc.player.getPitch()); + 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; @@ -312,7 +355,7 @@ public class Prediction extends Module { RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player) ); if (hitResult.getType() != HitResult.Type.MISS) { - lastPosition = new Vector3d(hitResult.getPos().x, hitResult.getPos().y, hitResult.getPos().z); + position = new Vector3d(hitResult.getPos().x, hitResult.getPos().y, hitResult.getPos().z); } Box box = new Box( @@ -432,35 +475,174 @@ public class Prediction extends Module { } } - private void calculateAngle(double tickDelta) { + private double calculateHeightOffset(Vector3d targetPosition, double pitch) { + double targetDistance = Math.sqrt( + (targetPosition.x - mc.player.getX()) * (targetPosition.x - mc.player.getX()) + + (targetPosition.z - mc.player.getZ()) * (targetPosition.z - mc.player.getZ()) + ); + + 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()), 0); + + double yaw = Rotations.getYaw(new Vec3d(targetPosition.x, targetPosition.y, targetPosition.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 : airDrag); + velocity.sub(0, gravity, 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) - targetPosition.y; + } + } + + return Double.NaN; + } + + private void calculateAngle() { if (targetEntity == null) return; - float velocity = (mc.player.getItemUseTime() - mc.player.getItemUseTimeLeft()) / 20f; - velocity = (velocity * velocity + velocity * 2) / 3; - if (velocity > 1) velocity = 1; + // parabolic prediction + if (predictionLevel.get() == 0) { + double posX = targetEntity.getPos().getX(); + double posY = targetEntity.getPos().getY(); + double posZ = targetEntity.getPos().getZ(); - double posX = targetEntity.getPos().getX() + (targetEntity.getPos().getX() - targetEntity.prevX) * tickDelta; - double posY = targetEntity.getPos().getY() + (targetEntity.getPos().getY() - targetEntity.prevY) * tickDelta; - double posZ = targetEntity.getPos().getZ() + (targetEntity.getPos().getZ() - targetEntity.prevZ) * tickDelta; + posY -= 1.9f - targetEntity.getHeight(); - posY -= 1.9f - targetEntity.getHeight(); + double relativeX = posX - mc.player.getX(); + double relativeY = posY - mc.player.getY(); + double relativeZ = posZ - mc.player.getZ(); - double relativeX = posX - mc.player.getX(); - double relativeY = posY - mc.player.getY(); - double relativeZ = posZ - mc.player.getZ(); + double hDistance = Math.sqrt(relativeX * relativeX + relativeZ * relativeZ); + double hDistanceSq = hDistance * hDistance; + float g = 0.006f; + float pitch = (float) -Math.toDegrees(Math.atan((1.0 - Math.sqrt(1.0 - g * (g * hDistanceSq + 2 * relativeY))) / (g * hDistance))); - double hDistance = Math.sqrt(relativeX * relativeX + relativeZ * relativeZ); - double hDistanceSq = hDistance * hDistance; - float g = 0.006f; - float velocitySq = velocity * velocity; - float pitch = (float) -Math.toDegrees(Math.atan((velocitySq - Math.sqrt(velocitySq * velocitySq - g * (g * hDistanceSq + 2 * relativeY * velocitySq))) / (g * hDistance))); + if (Float.isNaN(pitch)) { + targetYaw = Rotations.getYaw(targetEntity); + targetPitch = Rotations.getPitch(targetEntity); + } else { + targetYaw = Rotations.getYaw(targetEntity); + targetPitch = pitch; + } - if (Float.isNaN(pitch)) { - targetYaw = Rotations.getYaw(targetEntity); - targetPitch = Rotations.getPitch(targetEntity); - } else { - targetYaw = Rotations.getYaw(new Vec3d(posX, posY, posZ)); - targetPitch = pitch; + targetCharge = 1.0; + + calculatePath(); + return; + } + + Vector3d targetPosition = new Vector3d(targetEntity.getX(), targetEntity.getY() + targetEntity.getHeight() / 2.0, targetEntity.getZ()); + + // Basic physics prediction + if (predictionLevel.get() == 1) { + // Solve for the highest pitch + double highestPitch; + { + double minPitch = -90.0; + double maxPitch = 90.0; + for (int i = 0; iterationSteps.get() > 0 ? i < iterationSteps.get() : maxPitch - minPitch > iterationEpsilon.get(); i++) { + double mid1 = minPitch + (maxPitch - minPitch) / 3.0; + double mid2 = maxPitch - (maxPitch - minPitch) / 3.0; + double mid1Height = calculateHeightOffset(targetPosition, mid1); + double mid2Height = calculateHeightOffset(targetPosition, mid2); + if (Double.isNaN(mid1Height) || Double.isNaN(mid2Height)) return; + if (mid1Height < mid2Height) + minPitch = mid1; + else maxPitch = mid2; + } + highestPitch = (minPitch + maxPitch) / 2.0; + if (calculateHeightOffset(targetPosition, highestPitch) < 0.0) return; + } + + // Solve for the low pitch + { + targetLowPitch = highestPitch; + double minPitch = highestPitch; + double maxPitch = 90.0; + for (int i = 0; iterationSteps.get() > 0 ? i < iterationSteps.get() : maxPitch - minPitch > iterationEpsilon.get(); i++) { + double mid = (minPitch + maxPitch) / 2.0; + double midHeight = calculateHeightOffset(targetPosition, mid); + if (Double.isNaN(midHeight)) { + targetLowPitch = Double.NaN; + break; + } + if (midHeight <= 0) + maxPitch = mid; + else minPitch = mid; + } + if (!Double.isNaN(targetLowPitch)) targetLowPitch = (minPitch + maxPitch) / 2.0; + } + + if (!Double.isNaN(targetLowPitch)) + { + targetPitch = targetLowPitch; + targetYaw = Rotations.getYaw(targetEntity); + targetCharge = getCurrentCharge(); + + calculatePath(); + } + + // Solve for the high pitch + if (!isHitTarget && allowHighThrows.get()) { + targetHighPitch = highestPitch; + double minPitch = -90.0; + double maxPitch = highestPitch; + for (int i = 0; iterationSteps.get() > 0 ? i < iterationSteps.get() : maxPitch - minPitch > iterationEpsilon.get(); i++) { + double mid = (minPitch + maxPitch) / 2.0; + double midHeight = calculateHeightOffset(targetPosition, mid); + if (Double.isNaN(midHeight)) { + targetHighPitch = Double.NaN; + break; + } + if (midHeight <= 0) + minPitch = mid; + else maxPitch = mid; + } + if (!Double.isNaN(targetHighPitch)) targetHighPitch = (minPitch + maxPitch) / 2.0; + } + + if (!isHitTarget && !Double.isNaN(targetHighPitch) && allowHighThrows.get()) { + targetPitch = targetHighPitch; + targetYaw = Rotations.getYaw(targetEntity); + targetCharge = getCurrentCharge(); + + calculatePath(); + } + + return; } }