Add prediction based on physics simulations

This commit is contained in:
Redstone1024 2024-08-27 21:58:41 +08:00
parent 5b4a9fb908
commit fd19d4bf63

View File

@ -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<Double> predictionLevel = sgGeneral.add(new DoubleSetting.Builder()
private final Setting<Integer> 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<Integer> 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<Integer> 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<Double> 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<Boolean> 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<Boolean> 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<Double> 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<Integer> 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<Boolean> 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<Vector3d> vectorPool = new Pool<>(Vector3d::new);
private final List<Vector3d> 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;
}
private void calculatePath(double tickDelta) {
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;
private void calculatePath() {
clearPath();
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,16 +475,69 @@ 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;
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;
// parabolic prediction
if (predictionLevel.get() == 0) {
double posX = targetEntity.getPos().getX();
double posY = targetEntity.getPos().getY();
double posZ = targetEntity.getPos().getZ();
posY -= 1.9f - targetEntity.getHeight();
@ -452,16 +548,102 @@ public class Prediction extends Module {
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)));
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(new Vec3d(posX, posY, posZ));
targetYaw = Rotations.getYaw(targetEntity);
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;
}
}
private void aim(double tickDelta) {