Add prediction based on physics simulations
This commit is contained in:
parent
5b4a9fb908
commit
fd19d4bf63
@ -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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user