From d719e0e46d11b4abb7dd4b92639dc4e24d667eb7 Mon Sep 17 00:00:00 2001 From: Redstone1024 <2824517378@qq.com> Date: Mon, 26 Aug 2024 21:06:24 +0800 Subject: [PATCH] Implement bow projectile prediction --- .../java/com/example/addon/AddonTemplate.java | 3 + .../com/example/addon/modules/Prediction.java | 229 ++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 src/main/java/com/example/addon/modules/Prediction.java diff --git a/src/main/java/com/example/addon/AddonTemplate.java b/src/main/java/com/example/addon/AddonTemplate.java index 3bd07af..fcabb11 100644 --- a/src/main/java/com/example/addon/AddonTemplate.java +++ b/src/main/java/com/example/addon/AddonTemplate.java @@ -2,7 +2,9 @@ package com.example.addon; import com.example.addon.commands.CommandExample; import com.example.addon.hud.HudExample; +import com.example.addon.modules.AimAssist; import com.example.addon.modules.ModuleExample; +import com.example.addon.modules.Prediction; import com.mojang.logging.LogUtils; import meteordevelopment.meteorclient.addons.GithubRepo; import meteordevelopment.meteorclient.addons.MeteorAddon; @@ -24,6 +26,7 @@ public class AddonTemplate extends MeteorAddon { // Modules Modules.get().add(new ModuleExample()); + Modules.get().add(new Prediction()); // Commands Commands.add(new CommandExample()); diff --git a/src/main/java/com/example/addon/modules/Prediction.java b/src/main/java/com/example/addon/modules/Prediction.java new file mode 100644 index 0000000..686f799 --- /dev/null +++ b/src/main/java/com/example/addon/modules/Prediction.java @@ -0,0 +1,229 @@ +package com.example.addon.modules; + +import com.example.addon.AddonTemplate; +import meteordevelopment.meteorclient.events.render.Render3DEvent; +import meteordevelopment.meteorclient.renderer.ShapeMode; +import meteordevelopment.meteorclient.settings.*; +import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.utils.Utils; +import meteordevelopment.meteorclient.utils.misc.MissHitResult; +import meteordevelopment.meteorclient.utils.misc.Pool; +import meteordevelopment.meteorclient.utils.render.color.SettingColor; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +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.util.hit.BlockHitResult; +import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.*; +import net.minecraft.world.RaycastContext; +import org.joml.Vector3d; + +import java.util.ArrayList; +import java.util.List; + +public class Prediction extends Module { + private final SettingGroup sgGeneral = settings.getDefaultGroup(); + private final SettingGroup sgRender = settings.createGroup("Render"); + + public final Setting simulationSteps = sgGeneral.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 sideColor = sgRender.add(new ColorSetting.Builder() + .name("side-color") + .description("The side color.") + .defaultValue(new SettingColor(255, 150, 0, 35)) + .build() + ); + + private final Setting lineColor = sgRender.add(new ColorSetting.Builder() + .name("line-color") + .description("The line color.") + .defaultValue(new SettingColor(255, 150, 0)) + .build() + ); + + public Prediction() { super(AddonTemplate.CATEGORY, "bow-prediction", "Predicting arrow trajectories."); } + + 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 void clearPath() { + for (Vector3d point : points) vectorPool.free(point); + points.clear(); + + hitQuad = false; + collidingEntity = null; + } + + private void calculatePath(double tickDelta) { + clearPath(); + + 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; + + 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); + + double yaw = MathHelper.lerp(tickDelta, mc.player.prevYaw, mc.player.getYaw()); + double pitch = MathHelper.lerp(tickDelta, mc.player.prevPitch, mc.player.getPitch()); + + 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); + + HitResult hitResult = null; + + for (int i = 0; i < (simulationSteps.get() > 0 ? simulationSteps.get() : Integer.MAX_VALUE) && hitResult == null; i++) { + 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 : airDrag); + velocity.sub(0, gravity, 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) { + lastPosition = new Vector3d(hitResult.getPos().x, hitResult.getPos().y, hitResult.getPos().z); + } + + Box box = new Box( + lastPosition.x - (EntityType.ARROW.getWidth() / 2f), + lastPosition.y, + lastPosition.z - (EntityType.ARROW.getWidth() / 2f), + lastPosition.x + (EntityType.ARROW.getWidth() / 2f), + lastPosition.y + EntityType.ARROW.getHeight(), + lastPosition.z + (EntityType.ARROW.getWidth() / 2f) + ).stretch(velocity.x, velocity.y, velocity.z).expand(1.0D); + HitResult hitResultEntity = ProjectileUtil.getEntityCollision(mc.world, null, + new Vec3d(lastPosition.x, lastPosition.y, lastPosition.z), new Vec3d(position.x, position.y, position.z), + box, entity -> !entity.isSpectator() && entity.isAlive() && entity.canHit() && entity != mc.player + ); + if (hitResultEntity != null) { + hitResult = hitResultEntity; + } + + hitResult = hitResult.getType() == HitResult.Type.MISS ? null : hitResult; + } + + if (hitResult != null) { + if (hitResult.getType() == HitResult.Type.BLOCK) { + BlockHitResult r = (BlockHitResult) hitResult; + + hitQuad = true; + hitQuad1.set(r.getPos().x, r.getPos().y, r.getPos().z); + hitQuad2.set(r.getPos().x, r.getPos().y, r.getPos().z); + + if (r.getSide() == Direction.UP || r.getSide() == Direction.DOWN) { + hitQuadHorizontal = true; + hitQuad1.x -= 0.25; + hitQuad1.z -= 0.25; + hitQuad2.x += 0.25; + hitQuad2.z += 0.25; + } + else if (r.getSide() == Direction.NORTH || r.getSide() == 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())); + } + else if (hitResult.getType() == HitResult.Type.ENTITY) { + collidingEntity = ((EntityHitResult) hitResult).getEntity(); + points.add(Utils.set(vectorPool.get(), hitResult.getPos()).add(0, collidingEntity.getHeight() / 2, 0)); + } + } + } + + private void renderPath(Render3DEvent event) { + 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, lineColor.get()); } + lastPoint = point; + } + + if (hitQuad) { + if (hitQuadHorizontal) event.renderer.sideHorizontal(hitQuad1.x, hitQuad1.y, hitQuad1.z, hitQuad1.x + 0.5, hitQuad1.z + 0.5, sideColor.get(), lineColor.get(), ShapeMode.Both); + else event.renderer.sideVertical(hitQuad1.x, hitQuad1.y, hitQuad1.z, hitQuad2.x, hitQuad2.y, hitQuad2.z, sideColor.get(), lineColor.get(), ShapeMode.Both); + } + + if (collidingEntity != null) { + double x = (collidingEntity.getX() - collidingEntity.prevX) * event.tickDelta; + double y = (collidingEntity.getY() - collidingEntity.prevY) * event.tickDelta; + double z = (collidingEntity.getZ() - collidingEntity.prevZ) * event.tickDelta; + + Box box = collidingEntity.getBoundingBox(); + event.renderer.box(x + box.minX, y + box.minY, z + box.minZ, x + box.maxX, y + box.maxY, z + box.maxZ, sideColor.get(), lineColor.get(), ShapeMode.Both, 0); + } + } + + @EventHandler + private void onRender(Render3DEvent event) { + float tickDelta = mc.world.getTickManager().isFrozen() ? 1 : event.tickDelta; + calculatePath(tickDelta); + renderPath(event); + } + +}