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.FluidState;
import net.minecraft.fluid.Fluids; import net.minecraft.fluid.Fluids;
import net.minecraft.item.BowItem; import net.minecraft.item.BowItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.Items; import net.minecraft.item.Items;
import net.minecraft.util.hit.BlockHitResult; 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 sgSpeed = settings.createGroup("Aim Speed");
private final SettingGroup sgRender = settings.createGroup("Render"); 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") .name("prediction-level")
.description("The intelligence level for entity position prediction.") .description("The intelligence level for entity position prediction.")
.defaultValue(0) .defaultValue(0)
.range(0, 0) .range(0, 1)
.sliderMax(0) .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() .build()
); );
@ -63,6 +95,14 @@ public class Prediction extends Module {
.build() .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() private final Setting<Double> range = sgTarget.add(new DoubleSetting.Builder()
.name("range") .name("range")
.description("The maximum range the entity can be to aim at it.") .description("The maximum range the entity can be to aim at it.")
@ -108,14 +148,6 @@ public class Prediction extends Module {
.build() .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() private final Setting<Boolean> instant = sgSpeed.add(new BoolSetting.Builder()
.name("instant-look") .name("instant-look")
.description("Instantly looks at the entity.") .description("Instantly looks at the entity.")
@ -184,24 +216,25 @@ public class Prediction extends Module {
@Override @Override
public void onDeactivate() { public void onDeactivate() {
targetEntity = null; targetEntity = null;
isHitTarget = false;
} }
@EventHandler @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 @EventHandler
private void onRender(Render3DEvent event) { private void onRender(Render3DEvent event) {
float tickDelta = mc.world.getTickManager().isFrozen() ? 1 : event.tickDelta; float tickDelta = mc.world.getTickManager().isFrozen() ? 1 : event.tickDelta;
if (mc.options.attackKey.isPressed() || !isSelectableTarget(targetEntity)) { boolean canAim = aimAssist.get() && (isHitTarget || !aimOnlyHit.get()) && mc.options.useKey.isPressed() && InvUtils.testInHands(Items.BOW);
targetEntity = TargetUtils.get(this::isSelectableTarget, priority.get());
isHitTarget = false;
}
calculatePath(tickDelta); if (canAim) aim(event.tickDelta);
if (aimAssist.get()) calculateAngle(tickDelta);
if (aimAssist.get() && mc.options.useKey.isPressed() && InvUtils.testInHands(Items.BOW)) aim(event.tickDelta);
if (enableRender.get()) renderPath(event); if (enableRender.get()) renderPath(event);
} }
@ -225,18 +258,40 @@ public class Prediction extends Module {
return !(entity instanceof AnimalEntity) || babies.get() || !((AnimalEntity) entity).isBaby(); return !(entity instanceof AnimalEntity) || babies.get() || !((AnimalEntity) entity).isBaby();
} }
private Entity targetEntity; private double getCurrentCharge(){
private boolean isHitTarget; 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 targetYaw;
private double targetPitch; 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 Pool<Vector3d> vectorPool = new Pool<>(Vector3d::new);
private final List<Vector3d> points = new ArrayList<>(); private final List<Vector3d> points = new ArrayList<>();
private boolean hitQuad = false, hitQuadHorizontal = false; private boolean hitQuad = false, hitQuadHorizontal = false;
private final Vector3d hitQuad1 = new Vector3d(); private final Vector3d hitQuad1 = new Vector3d();
private final Vector3d hitQuad2 = new Vector3d(); private final Vector3d hitQuad2 = new Vector3d();
private Entity collidingEntity = null; private Entity collidingEntity = null;
private boolean isHitTarget;
private void clearPath() { private void clearPath() {
for (Vector3d point : points) vectorPool.free(point); for (Vector3d point : points) vectorPool.free(point);
@ -244,40 +299,28 @@ public class Prediction extends Module {
hitQuad = false; hitQuad = false;
collidingEntity = null; collidingEntity = null;
isHitTarget = false;
} }
private void calculatePath(double tickDelta) { private void calculatePath() {
clearPath(); clearPath();
isHitTarget = false;
ItemStack itemStack = mc.player.getMainHandStack(); double speed = targetCharge * 3;
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;
Vector3d lastPosition = new Vector3d(0.0, 0.0, 0.0); Vector3d lastPosition = new Vector3d(0.0, 0.0, 0.0);
Vector3d position = 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); 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 yaw = targetYaw;
double pitch = MathHelper.lerp(tickDelta, mc.player.prevPitch, mc.player.getPitch()); double pitch = targetPitch;
double x = -Math.sin(yaw * 0.017453292) * Math.cos(pitch * 0.017453292); double x = -Math.sin(yaw * 0.017453292) * Math.cos(pitch * 0.017453292);
double y = -Math.sin(pitch * 0.017453292); double y = -Math.sin(pitch * 0.017453292);
double z = Math.cos(yaw * 0.017453292) * Math.cos(pitch * 0.017453292); double z = Math.cos(yaw * 0.017453292) * Math.cos(pitch * 0.017453292);
velocity.set(x, y, z).normalize().mul(speed); 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; HitResult hitResult = null;
@ -312,7 +355,7 @@ public class Prediction extends Module {
RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player) RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player)
); );
if (hitResult.getType() != HitResult.Type.MISS) { 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( 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; if (targetEntity == null) return;
float velocity = (mc.player.getItemUseTime() - mc.player.getItemUseTimeLeft()) / 20f; // parabolic prediction
velocity = (velocity * velocity + velocity * 2) / 3; if (predictionLevel.get() == 0) {
if (velocity > 1) velocity = 1; 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; posY -= 1.9f - targetEntity.getHeight();
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(); 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 hDistance = Math.sqrt(relativeX * relativeX + relativeZ * relativeZ);
double relativeY = posY - mc.player.getY(); double hDistanceSq = hDistance * hDistance;
double relativeZ = posZ - mc.player.getZ(); 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); if (Float.isNaN(pitch)) {
double hDistanceSq = hDistance * hDistance; targetYaw = Rotations.getYaw(targetEntity);
float g = 0.006f; targetPitch = Rotations.getPitch(targetEntity);
float velocitySq = velocity * velocity; } else {
float pitch = (float) -Math.toDegrees(Math.atan((velocitySq - Math.sqrt(velocitySq * velocitySq - g * (g * hDistanceSq + 2 * relativeY * velocitySq))) / (g * hDistance))); targetYaw = Rotations.getYaw(targetEntity);
targetPitch = pitch;
}
if (Float.isNaN(pitch)) { targetCharge = 1.0;
targetYaw = Rotations.getYaw(targetEntity);
targetPitch = Rotations.getPitch(targetEntity); calculatePath();
} else { return;
targetYaw = Rotations.getYaw(new Vec3d(posX, posY, posZ)); }
targetPitch = pitch;
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;
} }
} }