From 862104232e86b29736756533bd59208cd8ece578 Mon Sep 17 00:00:00 2001 From: Wide_Cat Date: Mon, 22 Dec 2025 19:58:37 +0000 Subject: [PATCH 1/6] significant projectile simulator improvements known issues: - accurate mode seems broken - if the projectile spawns in water the drag isn't calculated properly - discrepancy between vanilla updating the water state and us checking the entities position --- .../meteorclient/mixin/Vec3dMixin.java | 12 +- .../meteorclient/mixininterface/IVec3d.java | 20 +- .../systems/modules/render/Trajectories.java | 6 +- .../entity/ProjectileEntitySimulator.java | 232 +++++++++++------- 4 files changed, 174 insertions(+), 96 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/mixin/Vec3dMixin.java b/src/main/java/meteordevelopment/meteorclient/mixin/Vec3dMixin.java index 818a571744..2349e8f786 100644 --- a/src/main/java/meteordevelopment/meteorclient/mixin/Vec3dMixin.java +++ b/src/main/java/meteordevelopment/meteorclient/mixin/Vec3dMixin.java @@ -19,20 +19,26 @@ public abstract class Vec3dMixin implements IVec3d { @Shadow @Final @Mutable public double z; @Override - public void meteor$set(double x, double y, double z) { + public Vec3d meteor$set(double x, double y, double z) { this.x = x; this.y = y; this.z = z; + + return (Vec3d) (Object) this; } @Override - public void meteor$setXZ(double x, double z) { + public Vec3d meteor$setXZ(double x, double z) { this.x = x; this.z = z; + + return (Vec3d) (Object) this; } @Override - public void meteor$setY(double y) { + public Vec3d meteor$setY(double y) { this.y = y; + + return (Vec3d) (Object) this; } } diff --git a/src/main/java/meteordevelopment/meteorclient/mixininterface/IVec3d.java b/src/main/java/meteordevelopment/meteorclient/mixininterface/IVec3d.java index a600199b1e..2cbb4c97da 100644 --- a/src/main/java/meteordevelopment/meteorclient/mixininterface/IVec3d.java +++ b/src/main/java/meteordevelopment/meteorclient/mixininterface/IVec3d.java @@ -5,21 +5,27 @@ package meteordevelopment.meteorclient.mixininterface; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3i; import org.joml.Vector3d; +@SuppressWarnings("UnusedReturnValue") public interface IVec3d { - void meteor$set(double x, double y, double z); + Vec3d meteor$set(double x, double y, double z); - default void meteor$set(Vec3i vec) { - meteor$set(vec.getX(), vec.getY(), vec.getZ()); + default Vec3d meteor$set(Vec3i vec) { + return meteor$set(vec.getX(), vec.getY(), vec.getZ()); } - default void meteor$set(Vector3d vec) { - meteor$set(vec.x, vec.y, vec.z); + default Vec3d meteor$set(Vector3d vec) { + return meteor$set(vec.x, vec.y, vec.z); } - void meteor$setXZ(double x, double z); + default Vec3d meteor$set(Vec3d pos) { + return meteor$set(pos.x, pos.y, pos.z); + } + + Vec3d meteor$setXZ(double x, double z); - void meteor$setY(double y); + Vec3d meteor$setY(double y); } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java index 34f769ddfb..1f682e3990 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java @@ -19,6 +19,7 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.ProjectileEntity; +import net.minecraft.entity.projectile.TridentEntity; import net.minecraft.entity.projectile.WitherSkullEntity; import net.minecraft.item.*; import net.minecraft.registry.Registries; @@ -221,7 +222,10 @@ private void onRender(Render3DEvent event) { if (firedProjectiles.get()) { for (Entity entity : mc.world.getEntities()) { - if (entity instanceof ProjectileEntity && (!ignoreWitherSkulls.get() || !(entity instanceof WitherSkullEntity))) { + if (entity instanceof ProjectileEntity) { + if (ignoreWitherSkulls.get() && entity instanceof WitherSkullEntity) continue; + if (entity instanceof TridentEntity trident && trident.noClip) continue; + calculateFiredPath(entity, tickDelta); for (Path path : paths) path.render(event); } diff --git a/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java b/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java index 54ebd68005..a53b872ec3 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java @@ -14,12 +14,14 @@ import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.ChargedProjectilesComponent; import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityDimensions; import net.minecraft.entity.EntityType; import net.minecraft.entity.projectile.*; import net.minecraft.entity.projectile.thrown.*; import net.minecraft.fluid.FluidState; -import net.minecraft.fluid.Fluids; import net.minecraft.item.*; +import net.minecraft.registry.tag.FluidTags; +import net.minecraft.registry.tag.TagKey; import net.minecraft.util.hit.HitResult; import net.minecraft.util.math.*; import net.minecraft.world.RaycastContext; @@ -29,53 +31,77 @@ import static meteordevelopment.meteorclient.MeteorClient.mc; public class ProjectileEntitySimulator { - private static final BlockPos.Mutable blockPos = new BlockPos.Mutable(); + private final BlockPos.Mutable blockPos = new BlockPos.Mutable(); - private static final Vec3d pos3d = new Vec3d(0, 0, 0); - private static final Vec3d prevPos3d = new Vec3d(0, 0, 0); + private final Vec3d pos3d = new Vec3d(0, 0, 0); + private final Vec3d prevPos3d = new Vec3d(0, 0, 0); public final Vector3d pos = new Vector3d(); private final Vector3d velocity = new Vector3d(); private Entity simulatingEntity; + private EntityDimensions dimensions; private double gravity; private double airDrag, waterDrag; - private float height, width; + public record MotionData(float power, float roll, double gravity, float airDrag, float waterDrag, EntityType entity) { + public MotionData withPower(float power) { + return new MotionData(power, this.roll(), this.gravity(), this.airDrag(), this.waterDrag(), this.entity()); + } + } + + // ThrownEntity + private static final MotionData EGG = new MotionData(1.5f, 0, 0.03, 0.99f, 0.8f, EntityType.EGG); + private static final MotionData ENDER_PEARL = new MotionData(1.5f, 0, 0.03, 0.99f, 0.8f, EntityType.ENDER_PEARL); + private static final MotionData SNOWBALL = new MotionData(1.5f, 0, 0.03, 0.99f, 0.8f, EntityType.SNOWBALL); + private static final MotionData EXPERIENCE_BOTTLE = new MotionData(0.7f, -20, 0.07, 0.99f, 0.8f, EntityType.EXPERIENCE_BOTTLE); + private static final MotionData LINGERING_POTION = new MotionData(0.5f, -20, 0.05, 0.99f, 0.8f, EntityType.LINGERING_POTION); + private static final MotionData SPLASH_POTION = new MotionData(0.5f, -20, 0.05, 0.99f, 0.8f, EntityType.SPLASH_POTION); + + // ExplosiveProjectileEntity + private static final MotionData EXPLOSIVE = new MotionData(0, 0, 0, 1, 1, null); // fireball, wither skull, etc. + private static final MotionData WIND_CHARGE = new MotionData(1.5f, 0, 0, 1, 1, EntityType.WIND_CHARGE); + + // PersistentProjectileEntity + private static final MotionData ARROW = new MotionData(0, 0, 0.05, 0.99f, 0.6f, EntityType.ARROW); + private static final MotionData TRIDENT = new MotionData(2.5f, 0, 0.05, 0.99f, 0.99f, EntityType.TRIDENT); + + // Other + private static final MotionData FIREWORK_ROCKET = new MotionData(0, 0, 0, 1, 1, EntityType.FIREWORK_ROCKET); + private static final MotionData FISHING_BOBBER = new MotionData(0, 0, 0.03, 0.92f, 0, EntityType.FISHING_BOBBER); + private static final MotionData LLAMA_SPIT = new MotionData(1.5f, 0, 0.06, 0.99f, 0, EntityType.LLAMA_SPIT); // held items - public boolean set(Entity user, ItemStack itemStack, double simulated, boolean accurate, float tickDelta) { + public boolean set(Entity user, ItemStack itemStack, double angleOffset, boolean accurate, float tickDelta) { Item item = itemStack.getItem(); switch (item) { case BowItem ignored -> { - double charge = BowItem.getPullProgress(mc.player.getItemUseTime()); + float charge = BowItem.getPullProgress(mc.player.getItemUseTime()); if (charge <= 0.1) return false; - set(user, 0, charge * 3, simulated, 0.05, 0.6, accurate, tickDelta, EntityType.ARROW); + set(user, angleOffset, accurate, tickDelta, ARROW.withPower(charge * 3)); } case CrossbowItem ignored -> { ChargedProjectilesComponent projectilesComponent = itemStack.get(DataComponentTypes.CHARGED_PROJECTILES); if (projectilesComponent == null) return false; + float speed = CrossbowItemAccessor.meteor$getSpeed(projectilesComponent); if (projectilesComponent.contains(Items.FIREWORK_ROCKET)) { - set(user, 0, CrossbowItemAccessor.meteor$getSpeed(projectilesComponent), simulated, 0, 0.6, accurate, tickDelta, EntityType.FIREWORK_ROCKET); + set(user, angleOffset, accurate, tickDelta, FIREWORK_ROCKET.withPower(speed)); } - else set(user, 0, CrossbowItemAccessor.meteor$getSpeed(projectilesComponent), simulated, 0.05, 0.6, accurate, tickDelta, EntityType.ARROW); - } - case WindChargeItem ignored -> { - set(user, 0, 1.5, simulated, 0, 1.0, accurate, tickDelta, EntityType.WIND_CHARGE); - this.airDrag = 1.0; + else set(user, angleOffset,accurate, tickDelta, ARROW.withPower(speed)); } - case FishingRodItem ignored -> setFishingBobber(user, tickDelta); - case TridentItem ignored -> set(user, 0, 2.5, simulated, 0.05, 0.99, accurate, tickDelta, EntityType.TRIDENT); - case SnowballItem ignored -> set(user, 0, 1.5, simulated, 0.03, 0.8, accurate, tickDelta, EntityType.SNOWBALL); - case EggItem ignored -> set(user, 0, 1.5, simulated, 0.03, 0.8, accurate, tickDelta, EntityType.EGG); - case EnderPearlItem ignored -> set(user, 0, 1.5, simulated, 0.03, 0.8, accurate, tickDelta, EntityType.ENDER_PEARL); - case ExperienceBottleItem ignored -> set(user, -20, 0.7, simulated, 0.07, 0.8, accurate, tickDelta, EntityType.EXPERIENCE_BOTTLE); - case SplashPotionItem ignored -> set(user, -20, 0.5, simulated, 0.05, 0.8, accurate, tickDelta, EntityType.SPLASH_POTION); - case LingeringPotionItem ignored -> set(user, -20, 0.5, simulated, 0.05, 0.8, accurate, tickDelta, EntityType.LINGERING_POTION); + case WindChargeItem ignored -> set(user, angleOffset, accurate, tickDelta, WIND_CHARGE); + case TridentItem ignored -> set(user, angleOffset, accurate, tickDelta, TRIDENT); + case SnowballItem ignored -> set(user, angleOffset, accurate, tickDelta, SNOWBALL); + case EggItem ignored -> set(user, angleOffset, accurate, tickDelta, EGG); + case EnderPearlItem ignored -> set(user, angleOffset, accurate, tickDelta, ENDER_PEARL); + case ExperienceBottleItem ignored -> set(user, angleOffset, accurate, tickDelta, EXPERIENCE_BOTTLE); + case SplashPotionItem ignored -> set(user, angleOffset, accurate, tickDelta, SPLASH_POTION); + case LingeringPotionItem ignored -> set(user, angleOffset, accurate, tickDelta, LINGERING_POTION); + case FishingRodItem ignored -> setFishingBobber(user, tickDelta, FISHING_BOBBER); default -> { return false; } @@ -84,7 +110,7 @@ public boolean set(Entity user, ItemStack itemStack, double simulated, boolean a return true; } - public void set(Entity user, double roll, double speed, double simulated, double gravity, double waterDrag, boolean accurate, float tickDelta, EntityType type) { + public void set(Entity user, double angleOffset, boolean accurate, float tickDelta, MotionData data) { Utils.set(pos, user, tickDelta).add(0, user.getEyeHeight(user.getPose()), 0); double yaw; @@ -100,14 +126,14 @@ public void set(Entity user, double roll, double speed, double simulated, double double x, y, z; - if (simulated == 0) { + if (angleOffset == 0) { x = -Math.sin(yaw * 0.017453292) * Math.cos(pitch * 0.017453292); - y = -Math.sin((pitch + roll) * 0.017453292); + y = -Math.sin((pitch + data.roll()) * 0.017453292); z = Math.cos(yaw * 0.017453292) * Math.cos(pitch * 0.017453292); } else { Vec3d vec3d = user.getOppositeRotationVector(1.0F); - Quaterniond quaternion = new Quaterniond().setAngleAxis(simulated, vec3d.x, vec3d.y, vec3d.z); + Quaterniond quaternion = new Quaterniond().setAngleAxis(angleOffset, vec3d.x, vec3d.y, vec3d.z); Vec3d vec3d2 = user.getRotationVec(1.0F); Vector3d vector3f = new Vector3d(vec3d2.x, vec3d2.y, vec3d2.z); vector3f.rotate(quaternion); @@ -117,19 +143,18 @@ public void set(Entity user, double roll, double speed, double simulated, double z = vector3f.z; } - velocity.set(x, y, z).normalize().mul(speed); + velocity.set(x, y, z).normalize().mul(data.power()); if (accurate) { Vec3d vel = user.getVelocity(); velocity.add(vel.x, user.isOnGround() ? 0.0D : vel.y, vel.z); } - this.simulatingEntity = type.create(mc.world, null); - this.gravity = gravity; - this.airDrag = 0.99; - this.waterDrag = waterDrag; - this.width = type.getWidth(); - this.height = type.getHeight(); + this.gravity = data.gravity(); + this.airDrag = data.airDrag(); + this.waterDrag = data.waterDrag(); + this.simulatingEntity = data.entity().create(mc.world, null); + this.dimensions = simulatingEntity.getDimensions(simulatingEntity.getPose()); } @@ -139,27 +164,22 @@ public boolean set(Entity entity, boolean accurate) { // skip entities in ground if (entity instanceof ProjectileInGroundAccessor ppe && ppe.meteor$invokeIsInGround()) return false; - if (entity instanceof ArrowEntity) { - set(entity, 0.05, 0.6, accurate); - } else if (entity instanceof TridentEntity) { - set(entity, 0.05, 0.99, accurate); - } - - else if (entity instanceof EnderPearlEntity || entity instanceof SnowballEntity || entity instanceof EggEntity) { - set(entity, 0.03, 0.8, accurate); - } else if (entity instanceof ExperienceBottleEntity) { - set(entity, 0.07, 0.8, accurate); - } else if (entity instanceof PotionEntity) { - set(entity, 0.05, 0.8, accurate); - } - - else if (entity instanceof WitherSkullEntity || entity instanceof FireballEntity || entity instanceof DragonFireballEntity || entity instanceof WindChargeEntity) { - // drag isn't actually 1, but this provides accurate results in 99.9% in of real situations. - set(entity, 0, 1.0, accurate); - this.airDrag = 1.0; - } - else { - return false; + switch (entity) { + case ArrowEntity e -> set(e, accurate, ARROW); + case SpectralArrowEntity e -> set(e, accurate, ARROW); + case TridentEntity e -> set(e, accurate, TRIDENT); + case EnderPearlEntity e -> set(e, accurate, ENDER_PEARL); + case SnowballEntity e -> set(e, accurate, SNOWBALL); + case EggEntity e -> set(e, accurate, EGG); + case ExperienceBottleEntity e -> set(e, accurate, EXPERIENCE_BOTTLE); + case SplashPotionEntity e -> set(e, accurate, SPLASH_POTION); + case LingeringPotionEntity e -> set(e, accurate, LINGERING_POTION); + case AbstractWindChargeEntity e -> set(e, accurate, WIND_CHARGE); + case ExplosiveProjectileEntity e -> set(e, accurate, EXPLOSIVE); + case LlamaSpitEntity e -> set(e, accurate, LLAMA_SPIT); + default -> { + return false; + } } if (entity.hasNoGravity()) { @@ -169,7 +189,7 @@ else if (entity instanceof WitherSkullEntity || entity instanceof FireballEntity return true; } - public void set(Entity entity, double gravity, double waterDrag, boolean accurate) { + public void set(Entity entity, boolean accurate, MotionData data) { pos.set(entity.getX(), entity.getY(), entity.getZ()); double speed = entity.getVelocity().length(); @@ -180,15 +200,14 @@ public void set(Entity entity, double gravity, double waterDrag, boolean accurat velocity.add(vel.x, entity.isOnGround() ? 0.0D : vel.y, vel.z); } + this.gravity = data.gravity(); + this.airDrag = data.airDrag(); + this.waterDrag = data.waterDrag(); this.simulatingEntity = entity; - this.gravity = gravity; - this.airDrag = 0.99; - this.waterDrag = waterDrag; - this.width = entity.getWidth(); - this.height = entity.getHeight(); + this.dimensions = simulatingEntity.getDimensions(simulatingEntity.getPose()); } - public void setFishingBobber(Entity user, float tickDelta) { + public void setFishingBobber(Entity user, float tickDelta, MotionData data) { double yaw; double pitch; @@ -212,22 +231,36 @@ public void setFishingBobber(Entity user, float tickDelta) { double l = velocity.length(); velocity.mul(0.6 / l + 0.5, 0.6 / l + 0.5, 0.6 / l + 0.5); - simulatingEntity = EntityType.FISHING_BOBBER.create(mc.world, null); - gravity = 0.03; - airDrag = 0.92; - waterDrag = 0; - width = EntityType.FISHING_BOBBER.getWidth(); - height = EntityType.FISHING_BOBBER.getHeight(); + this.simulatingEntity = data.entity().create(mc.world, null); + this.dimensions = simulatingEntity.getDimensions(simulatingEntity.getPose()); + this.gravity = data.gravity(); + this.airDrag = data.airDrag(); + this.waterDrag = data.waterDrag(); } + // https://minecraft.wiki/w/Projectile + // https://minecraft.wiki/w/Entity#Motion public HitResult tick() { - // Apply velocity ((IVec3d) prevPos3d).meteor$set(pos); - pos.add(velocity); - // Update velocity - velocity.mul(isTouchingWater() ? waterDrag : airDrag); - velocity.sub(0, gravity, 0); + // gravity -> drag -> position + if (simulatingEntity instanceof ThrownEntity || simulatingEntity instanceof ExplosiveProjectileEntity) { + velocity.sub(0, gravity, 0); + velocity.mul(isTouchingWater() ? waterDrag : airDrag); + pos.add(velocity); + } + // position -> drag -> gravity + else if (simulatingEntity instanceof PersistentProjectileEntity || simulatingEntity instanceof LlamaSpitEntity) { + pos.add(velocity); + velocity.mul(isTouchingWater() ? waterDrag : airDrag); + velocity.sub(0, gravity, 0); + } + // gravity -> position > drag + else if (simulatingEntity instanceof FishingBobberEntity) { + velocity.sub(0, gravity, 0); + pos.add(velocity); + velocity.mul(isTouchingWater() ? waterDrag : airDrag); + } // Check if below world if (pos.y < mc.world.getBottomY()) return MissHitResult.INSTANCE; @@ -242,34 +275,63 @@ public HitResult tick() { if (pos3d.equals(prevPos3d)) return MissHitResult.INSTANCE; HitResult hitResult = getCollision(); - return hitResult.getType() == HitResult.Type.MISS ? null : hitResult; } - private boolean isTouchingWater() { - blockPos.set(pos.x, pos.y, pos.z); - - FluidState fluidState = mc.world.getFluidState(blockPos); - if (fluidState.getFluid() != Fluids.WATER && fluidState.getFluid() != Fluids.FLOWING_WATER) return false; + /** + * {@link Entity#updateMovementInFluid(TagKey, double)} + */ + public boolean isTouchingWater() { + Box box = dimensions.getBoxAt(pos.x, pos.y, pos.z).contract(0.001); + int minX = MathHelper.floor(box.minX); + int maxX = MathHelper.ceil(box.maxX); + int minY = MathHelper.floor(box.minY); + int maxY = MathHelper.ceil(box.maxY); + int minZ = MathHelper.floor(box.minZ); + int maxZ = MathHelper.ceil(box.maxZ); + + for (int x = minX; x < maxX; x++) { + for (int y = minY; y < maxY; y++) { + for (int z = minZ; z < maxZ; z++) { + blockPos.set(x, y, z); + FluidState fluidState = mc.world.getFluidState(blockPos); + if (fluidState.isIn(FluidTags.WATER)) { + double fluidY = y + fluidState.getHeight(mc.world, blockPos); + if (fluidY >= box.minY) { + return true; + } + } + } + } + } - return pos.y - (int) pos.y <= fluidState.getHeight(); + return false; } + /** + * {@link ProjectileUtil#getCollision(Vec3d, Entity, java.util.function.Predicate, Vec3d, net.minecraft.world.World, float, RaycastContext.ShapeType)} + * + * Vanilla checks from the current to the next position. We check from the previous to the current positions - it + * solves the issue of the collision check from the starting position not working properly + */ private HitResult getCollision() { - HitResult hitResult = mc.world.raycast(new RaycastContext(prevPos3d, pos3d, RaycastContext.ShapeType.COLLIDER, waterDrag == 0 ? RaycastContext.FluidHandling.ANY : RaycastContext.FluidHandling.NONE, simulatingEntity)); + HitResult hitResult = mc.world.raycast(new RaycastContext( + prevPos3d, + pos3d, + RaycastContext.ShapeType.COLLIDER, + waterDrag == 0 ? RaycastContext.FluidHandling.ANY : RaycastContext.FluidHandling.NONE, + simulatingEntity + )); if (hitResult.getType() != HitResult.Type.MISS) { - ((IVec3d) pos3d).meteor$set(hitResult.getPos().x, hitResult.getPos().y, hitResult.getPos().z); + ((IVec3d) pos3d).meteor$set(hitResult.getPos()); } - // Vanilla uses the current and next positions to check collisions, we use the previous and current positions - Box box = new Box(prevPos3d.x - (width / 2f), prevPos3d.y, prevPos3d.z - (width / 2f), prevPos3d.x + (width / 2f), prevPos3d.y + height, prevPos3d.z + (width / 2f)) - .stretch(velocity.x, velocity.y, velocity.z).expand(1.0D); HitResult hitResult2 = ProjectileUtil.getEntityCollision( mc.world, simulatingEntity, prevPos3d, pos3d, - box, + dimensions.getBoxAt(pos3d).stretch(velocity.x, velocity.y, velocity.z).expand(1.0D), entity -> !entity.isSpectator() && entity.isAlive() && entity.canHit(), ProjectileUtil.getToleranceMargin(simulatingEntity) ); From e5eab9ce3ff4c51be464111d63f9e94ffbaabf0e Mon Sep 17 00:00:00 2001 From: Wide_Cat Date: Tue, 23 Dec 2025 23:40:05 +0000 Subject: [PATCH 2/6] fix issues and further improve the simulation accuracy --- .../systems/modules/combat/ArrowDodge.java | 14 +- .../systems/modules/render/Trajectories.java | 2 +- .../entity/ProjectileEntitySimulator.java | 142 ++++++++++-------- 3 files changed, 84 insertions(+), 74 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/ArrowDodge.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/ArrowDodge.java index ba8236dfca..e99744569d 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/ArrowDodge.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/ArrowDodge.java @@ -15,6 +15,7 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.projectile.ArrowEntity; import net.minecraft.entity.projectile.ProjectileEntity; +import net.minecraft.entity.projectile.SpectralArrowEntity; import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; @@ -54,13 +55,6 @@ public class ArrowDodge extends Module { .build() ); - private final Setting accurate = sgGeneral.add(new BoolSetting.Builder() - .name("accurate") - .description("Whether or not to calculate more accurate.") - .defaultValue(false) - .build() - ); - private final Setting groundCheck = sgGeneral.add(new BoolSetting.Builder() .name("ground-check") .description("Tries to prevent you from falling to your death.") @@ -78,7 +72,7 @@ public class ArrowDodge extends Module { private final Setting ignoreOwn = sgGeneral.add(new BoolSetting.Builder() .name("ignore-own") .description("Ignore your own projectiles.") - .defaultValue(false) + .defaultValue(true) .build() ); @@ -116,13 +110,13 @@ private void onTick(TickEvent.Pre event) { for (Entity e : mc.world.getEntities()) { if (!(e instanceof ProjectileEntity projectile)) continue; - if (!allProjectiles.get() && !(projectile instanceof ArrowEntity)) continue; + if (!allProjectiles.get() && !(projectile instanceof ArrowEntity || projectile instanceof SpectralArrowEntity)) continue; if (ignoreOwn.get()) { Entity owner = projectile.getOwner(); if (owner != null && owner.getUuid().equals(mc.player.getUuid())) continue; } - if (!simulator.set(projectile, accurate.get())) continue; + if (!simulator.set(projectile)) continue; for (int i = 0; i < (simulationSteps.get() > 0 ? simulationSteps.get() : Integer.MAX_VALUE); i++) { points.add(vec3s.get().set(simulator.pos)); if (simulator.tick() != null) break; diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java index 1f682e3990..45b390e2f1 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java @@ -205,7 +205,7 @@ private void calculateFiredPath(Entity entity, double tickDelta) { for (Path path : paths) path.clear(); // Calculate paths - if (!simulator.set(entity, accurate.get())) return; + if (!simulator.set(entity)) return; getEmptyPath().setStart(entity, tickDelta).calculate(); } diff --git a/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java b/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java index a53b872ec3..c495527bf8 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java @@ -8,6 +8,9 @@ import meteordevelopment.meteorclient.mixin.CrossbowItemAccessor; import meteordevelopment.meteorclient.mixin.ProjectileInGroundAccessor; import meteordevelopment.meteorclient.mixininterface.IVec3d; +import meteordevelopment.meteorclient.systems.modules.Modules; +import meteordevelopment.meteorclient.systems.modules.movement.NoSlow; +import meteordevelopment.meteorclient.systems.modules.movement.Sneak; import meteordevelopment.meteorclient.utils.Utils; import meteordevelopment.meteorclient.utils.misc.MissHitResult; import meteordevelopment.meteorclient.utils.player.Rotations; @@ -15,6 +18,7 @@ import net.minecraft.component.type.ChargedProjectilesComponent; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityDimensions; +import net.minecraft.entity.EntityPose; import net.minecraft.entity.EntityType; import net.minecraft.entity.projectile.*; import net.minecraft.entity.projectile.thrown.*; @@ -42,7 +46,8 @@ public class ProjectileEntitySimulator { private Entity simulatingEntity; private EntityDimensions dimensions; private double gravity; - private double airDrag, waterDrag; + private float airDrag, waterDrag; + private boolean isTouchingWater; public record MotionData(float power, float roll, double gravity, float airDrag, float waterDrag, EntityType entity) { public MotionData withPower(float power) { @@ -50,6 +55,9 @@ public MotionData withPower(float power) { } } + // https://minecraft.wiki/w/Projectile + // https://minecraft.wiki/w/Entity#Motion + // ThrownEntity private static final MotionData EGG = new MotionData(1.5f, 0, 0.03, 0.99f, 0.8f, EntityType.EGG); private static final MotionData ENDER_PEARL = new MotionData(1.5f, 0, 0.03, 0.99f, 0.8f, EntityType.ENDER_PEARL); @@ -71,6 +79,7 @@ public MotionData withPower(float power) { private static final MotionData FISHING_BOBBER = new MotionData(0, 0, 0.03, 0.92f, 0, EntityType.FISHING_BOBBER); private static final MotionData LLAMA_SPIT = new MotionData(1.5f, 0, 0.06, 0.99f, 0, EntityType.LLAMA_SPIT); + // held items public boolean set(Entity user, ItemStack itemStack, double angleOffset, boolean accurate, float tickDelta) { @@ -111,7 +120,11 @@ public boolean set(Entity user, ItemStack itemStack, double angleOffset, boolean } public void set(Entity user, double angleOffset, boolean accurate, float tickDelta, MotionData data) { - Utils.set(pos, user, tickDelta).add(0, user.getEyeHeight(user.getPose()), 0); + // I lost my mind for an hour trying to figure out why arrows and tridents were spawning lower than expected, + // and it was because no slow air strict was silently causing the player to crouch AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + EntityPose pose = user.getPose(); + if (user == mc.player && (Modules.get().get(NoSlow.class).airStrict() || Modules.get().get(Sneak.class).doPacket())) pose = EntityPose.CROUCHING; + Utils.set(pos, user, tickDelta).add(0, user.getEyeHeight(pose) - 0.1f, 0); double yaw; double pitch; @@ -146,7 +159,7 @@ public void set(Entity user, double angleOffset, boolean accurate, float tickDel velocity.set(x, y, z).normalize().mul(data.power()); if (accurate) { - Vec3d vel = user.getVelocity(); + Vec3d vel = user.getMovement(); velocity.add(vel.x, user.isOnGround() ? 0.0D : vel.y, vel.z); } @@ -155,28 +168,63 @@ public void set(Entity user, double angleOffset, boolean accurate, float tickDel this.waterDrag = data.waterDrag(); this.simulatingEntity = data.entity().create(mc.world, null); this.dimensions = simulatingEntity.getDimensions(simulatingEntity.getPose()); + this.isTouchingWater = simulatingEntity.isTouchingWater(); + } + + public void setFishingBobber(Entity user, float tickDelta, MotionData data) { + double yaw; + double pitch; + + if (user == mc.player && Rotations.rotating) { + yaw = Rotations.serverYaw; + pitch = Rotations.serverPitch; + } else { + yaw = user.getYaw(tickDelta); + pitch = user.getPitch(tickDelta); + } + + double h = Math.cos(-yaw * 0.017453292F - 3.1415927F); + double i = Math.sin(-yaw * 0.017453292F - 3.1415927F); + double j = -Math.cos(-pitch * 0.017453292F); + double k = Math.sin(-pitch * 0.017453292F); + + EntityPose pose = user.getPose(); + if (user == mc.player && (Modules.get().get(NoSlow.class).airStrict() || Modules.get().get(Sneak.class).doPacket())) pose = EntityPose.CROUCHING; + Utils.set(pos, user, tickDelta).sub(i * 0.3, 0, h * 0.3).add(0, user.getEyeHeight(pose), 0); + + velocity.set(-i, MathHelper.clamp(-(k / j), -5, 5), -h); + + double l = velocity.length(); + velocity.mul(0.6 / l + 0.5, 0.6 / l + 0.5, 0.6 / l + 0.5); + + this.simulatingEntity = data.entity().create(mc.world, null); + this.dimensions = simulatingEntity.getDimensions(simulatingEntity.getPose()); + this.gravity = data.gravity(); + this.airDrag = data.airDrag(); + this.waterDrag = data.waterDrag(); + this.isTouchingWater = simulatingEntity.isTouchingWater(); } // fired projectiles - public boolean set(Entity entity, boolean accurate) { + public boolean set(Entity entity) { // skip entities in ground if (entity instanceof ProjectileInGroundAccessor ppe && ppe.meteor$invokeIsInGround()) return false; switch (entity) { - case ArrowEntity e -> set(e, accurate, ARROW); - case SpectralArrowEntity e -> set(e, accurate, ARROW); - case TridentEntity e -> set(e, accurate, TRIDENT); - case EnderPearlEntity e -> set(e, accurate, ENDER_PEARL); - case SnowballEntity e -> set(e, accurate, SNOWBALL); - case EggEntity e -> set(e, accurate, EGG); - case ExperienceBottleEntity e -> set(e, accurate, EXPERIENCE_BOTTLE); - case SplashPotionEntity e -> set(e, accurate, SPLASH_POTION); - case LingeringPotionEntity e -> set(e, accurate, LINGERING_POTION); - case AbstractWindChargeEntity e -> set(e, accurate, WIND_CHARGE); - case ExplosiveProjectileEntity e -> set(e, accurate, EXPLOSIVE); - case LlamaSpitEntity e -> set(e, accurate, LLAMA_SPIT); + case ArrowEntity e -> set(e, ARROW); + case SpectralArrowEntity e -> set(e, ARROW); + case TridentEntity e -> set(e, TRIDENT); + case EnderPearlEntity e -> set(e, ENDER_PEARL); + case SnowballEntity e -> set(e, SNOWBALL); + case EggEntity e -> set(e, EGG); + case ExperienceBottleEntity e -> set(e, EXPERIENCE_BOTTLE); + case SplashPotionEntity e -> set(e, SPLASH_POTION); + case LingeringPotionEntity e -> set(e, LINGERING_POTION); + case AbstractWindChargeEntity e -> set(e, WIND_CHARGE); + case ExplosiveProjectileEntity e -> set(e, EXPLOSIVE); + case LlamaSpitEntity e -> set(e, LLAMA_SPIT); default -> { return false; } @@ -189,77 +237,44 @@ public boolean set(Entity entity, boolean accurate) { return true; } - public void set(Entity entity, boolean accurate, MotionData data) { + public void set(Entity entity, MotionData data) { pos.set(entity.getX(), entity.getY(), entity.getZ()); double speed = entity.getVelocity().length(); velocity.set(entity.getVelocity().x, entity.getVelocity().y, entity.getVelocity().z).normalize().mul(speed); - if (accurate) { - Vec3d vel = entity.getVelocity(); - velocity.add(vel.x, entity.isOnGround() ? 0.0D : vel.y, vel.z); - } - this.gravity = data.gravity(); this.airDrag = data.airDrag(); this.waterDrag = data.waterDrag(); this.simulatingEntity = entity; this.dimensions = simulatingEntity.getDimensions(simulatingEntity.getPose()); + this.isTouchingWater = simulatingEntity.isTouchingWater(); } - public void setFishingBobber(Entity user, float tickDelta, MotionData data) { - double yaw; - double pitch; - - if (user == mc.player && Rotations.rotating) { - yaw = Rotations.serverYaw; - pitch = Rotations.serverPitch; - } else { - yaw = user.getYaw(tickDelta); - pitch = user.getPitch(tickDelta); - } - - double h = Math.cos(-yaw * 0.017453292F - 3.1415927F); - double i = Math.sin(-yaw * 0.017453292F - 3.1415927F); - double j = -Math.cos(-pitch * 0.017453292F); - double k = Math.sin(-pitch * 0.017453292F); - - Utils.set(pos, user, tickDelta).sub(i * 0.3, 0, h * 0.3).add(0, user.getEyeHeight(user.getPose()), 0); - - velocity.set(-i, MathHelper.clamp(-(k / j), -5, 5), -h); - - double l = velocity.length(); - velocity.mul(0.6 / l + 0.5, 0.6 / l + 0.5, 0.6 / l + 0.5); - - this.simulatingEntity = data.entity().create(mc.world, null); - this.dimensions = simulatingEntity.getDimensions(simulatingEntity.getPose()); - this.gravity = data.gravity(); - this.airDrag = data.airDrag(); - this.waterDrag = data.waterDrag(); - } - - // https://minecraft.wiki/w/Projectile - // https://minecraft.wiki/w/Entity#Motion public HitResult tick() { ((IVec3d) prevPos3d).meteor$set(pos); // gravity -> drag -> position if (simulatingEntity instanceof ThrownEntity || simulatingEntity instanceof ExplosiveProjectileEntity) { velocity.sub(0, gravity, 0); - velocity.mul(isTouchingWater() ? waterDrag : airDrag); + velocity.mul(isTouchingWater ? waterDrag : airDrag); pos.add(velocity); + tickIsTouchingWater(); } // position -> drag -> gravity else if (simulatingEntity instanceof PersistentProjectileEntity || simulatingEntity instanceof LlamaSpitEntity) { pos.add(velocity); - velocity.mul(isTouchingWater() ? waterDrag : airDrag); + velocity.mul(isTouchingWater ? waterDrag : airDrag); velocity.sub(0, gravity, 0); + tickIsTouchingWater(); } // gravity -> position > drag - else if (simulatingEntity instanceof FishingBobberEntity) { + // accurate for fishing bobbers and firework rockets + else if (simulatingEntity instanceof ProjectileEntity) { + tickIsTouchingWater(); velocity.sub(0, gravity, 0); pos.add(velocity); - velocity.mul(isTouchingWater() ? waterDrag : airDrag); + velocity.mul(isTouchingWater ? waterDrag : airDrag); } // Check if below world @@ -281,7 +296,7 @@ else if (simulatingEntity instanceof FishingBobberEntity) { /** * {@link Entity#updateMovementInFluid(TagKey, double)} */ - public boolean isTouchingWater() { + public void tickIsTouchingWater() { Box box = dimensions.getBoxAt(pos.x, pos.y, pos.z).contract(0.001); int minX = MathHelper.floor(box.minX); int maxX = MathHelper.ceil(box.maxX); @@ -298,14 +313,15 @@ public boolean isTouchingWater() { if (fluidState.isIn(FluidTags.WATER)) { double fluidY = y + fluidState.getHeight(mc.world, blockPos); if (fluidY >= box.minY) { - return true; + isTouchingWater = true; + return; } } } } } - return false; + isTouchingWater = false; } /** @@ -315,7 +331,7 @@ public boolean isTouchingWater() { * solves the issue of the collision check from the starting position not working properly */ private HitResult getCollision() { - HitResult hitResult = mc.world.raycast(new RaycastContext( + HitResult hitResult = mc.world.getCollisionsIncludingWorldBorder(new RaycastContext( prevPos3d, pos3d, RaycastContext.ShapeType.COLLIDER, From 26f4034ca4be771e5ca1824768557257c25f7e94 Mon Sep 17 00:00:00 2001 From: Wide_Cat Date: Wed, 24 Dec 2025 18:20:38 +0000 Subject: [PATCH 3/6] assorted - fix the simulator thinking projectiles will collide with you when they shouldn't - make Trajectories render the arrow path when you hold an uncharged bow - add an option to skip rendering the first few ticks of a path for better visibility --- .../systems/modules/render/Trajectories.java | 34 ++++++++++--- .../entity/ProjectileEntitySimulator.java | 48 +++++++++++-------- 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java index 45b390e2f1..d99c1b22d9 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java @@ -87,6 +87,14 @@ public class Trajectories extends Module { // Render + private final Setting ignoreFirstTicks = sgRender.add(new IntSetting.Builder() + .name("ignore-rendering-first-ticks") + .description("Ignores rendering the first ticks, to make the rest of the path more visible.") + .defaultValue(3) + .min(0) + .build() + ); + private final Setting shapeMode = sgRender.add(new EnumSetting.Builder() .name("shape-mode") .description("How the shapes are rendered.") @@ -325,13 +333,27 @@ else if (result.getType() == HitResult.Type.ENTITY) { public void render(Render3DEvent event) { // Render path - for (Vector3d point : points) { - if (lastPoint != null) { - event.renderer.line(lastPoint.x, lastPoint.y, lastPoint.z, point.x, point.y, point.z, lineColor.get()); - if (renderPositionBox.get()) - event.renderer.box(point.x - positionBoxSize.get(), point.y - positionBoxSize.get(), point.z - positionBoxSize.get(), - point.x + positionBoxSize.get(), point.y + positionBoxSize.get(), point.z + positionBoxSize.get(), positionSideColor.get(), positionLineColor.get(), shapeMode.get(), 0); + + for (int i = (points.size() <= ignoreFirstTicks.get() ? 0 : ignoreFirstTicks.get()); i < points.size(); i++) { + Vector3d point = points.get(i); + + if (lastPoint == null) { + if (i > 1) lastPoint = points.get(i - 1); + else { + lastPoint = point; + continue; + } + } + + event.renderer.line(lastPoint.x, lastPoint.y, lastPoint.z, point.x, point.y, point.z, lineColor.get()); + if (renderPositionBox.get()) { + event.renderer.box( + point.x - positionBoxSize.get(), point.y - positionBoxSize.get(), point.z - positionBoxSize.get(), + point.x + positionBoxSize.get(), point.y + positionBoxSize.get(), point.z + positionBoxSize.get(), + positionSideColor.get(), positionLineColor.get(), shapeMode.get(), 0 + ); } + lastPoint = point; } diff --git a/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java b/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java index c495527bf8..a6e13cf70e 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java @@ -16,10 +16,7 @@ import meteordevelopment.meteorclient.utils.player.Rotations; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.ChargedProjectilesComponent; -import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityDimensions; -import net.minecraft.entity.EntityPose; -import net.minecraft.entity.EntityType; +import net.minecraft.entity.*; import net.minecraft.entity.projectile.*; import net.minecraft.entity.projectile.thrown.*; import net.minecraft.fluid.FluidState; @@ -45,6 +42,7 @@ public class ProjectileEntitySimulator { private Entity simulatingEntity; private EntityDimensions dimensions; + private int age; private double gravity; private float airDrag, waterDrag; private boolean isTouchingWater; @@ -87,8 +85,13 @@ public boolean set(Entity user, ItemStack itemStack, double angleOffset, boolean switch (item) { case BowItem ignored -> { - float charge = BowItem.getPullProgress(mc.player.getItemUseTime()); - if (charge <= 0.1) return false; + if (!(user instanceof LivingEntity livingEntity)) return false; + float charge = BowItem.getPullProgress(livingEntity.getItemUseTime()); + + if (charge <= 0.1) { + if (user == mc.player) charge = 1; + else return false; + } set(user, angleOffset, accurate, tickDelta, ARROW.withPower(charge * 3)); } @@ -163,12 +166,7 @@ public void set(Entity user, double angleOffset, boolean accurate, float tickDel velocity.add(vel.x, user.isOnGround() ? 0.0D : vel.y, vel.z); } - this.gravity = data.gravity(); - this.airDrag = data.airDrag(); - this.waterDrag = data.waterDrag(); - this.simulatingEntity = data.entity().create(mc.world, null); - this.dimensions = simulatingEntity.getDimensions(simulatingEntity.getPose()); - this.isTouchingWater = simulatingEntity.isTouchingWater(); + setSimulationData(data.entity().create(mc.world, null), data); } public void setFishingBobber(Entity user, float tickDelta, MotionData data) { @@ -197,12 +195,7 @@ public void setFishingBobber(Entity user, float tickDelta, MotionData data) { double l = velocity.length(); velocity.mul(0.6 / l + 0.5, 0.6 / l + 0.5, 0.6 / l + 0.5); - this.simulatingEntity = data.entity().create(mc.world, null); - this.dimensions = simulatingEntity.getDimensions(simulatingEntity.getPose()); - this.gravity = data.gravity(); - this.airDrag = data.airDrag(); - this.waterDrag = data.waterDrag(); - this.isTouchingWater = simulatingEntity.isTouchingWater(); + setSimulationData(data.entity().create(mc.world, null), data); } @@ -243,15 +236,21 @@ public void set(Entity entity, MotionData data) { double speed = entity.getVelocity().length(); velocity.set(entity.getVelocity().x, entity.getVelocity().y, entity.getVelocity().z).normalize().mul(speed); + setSimulationData(entity, data); + } + + private void setSimulationData(Entity entity, MotionData data) { this.gravity = data.gravity(); this.airDrag = data.airDrag(); this.waterDrag = data.waterDrag(); this.simulatingEntity = entity; this.dimensions = simulatingEntity.getDimensions(simulatingEntity.getPose()); this.isTouchingWater = simulatingEntity.isTouchingWater(); + this.age = simulatingEntity.age; } public HitResult tick() { + age++; ((IVec3d) prevPos3d).meteor$set(pos); // gravity -> drag -> position @@ -327,8 +326,10 @@ public void tickIsTouchingWater() { /** * {@link ProjectileUtil#getCollision(Vec3d, Entity, java.util.function.Predicate, Vec3d, net.minecraft.world.World, float, RaycastContext.ShapeType)} * - * Vanilla checks from the current to the next position. We check from the previous to the current positions - it - * solves the issue of the collision check from the starting position not working properly + * Vanilla checks from the current to the next position, while we check from the previous to the current positions. + * This solves the issue of the collision check from the starting position not working properly - otherwise, the + * simulated projectile may move from its start position through a block, only running the collision check afterwards. + * The vanilla game has other code to deal with this but this is the easiest way for us to fix it. */ private HitResult getCollision() { HitResult hitResult = mc.world.getCollisionsIncludingWorldBorder(new RaycastContext( @@ -342,12 +343,17 @@ private HitResult getCollision() { ((IVec3d) pos3d).meteor$set(hitResult.getPos()); } + // When entities are spawned, they first move and then check collisions against their current and next positions. + // Since we move and then check from the previous to the current position, it can sometimes detect thrown + // projectiles as colliding against ourselves, which does not happen for the first tick of movement. + if (age <= 1) return hitResult; + HitResult hitResult2 = ProjectileUtil.getEntityCollision( mc.world, simulatingEntity, prevPos3d, pos3d, - dimensions.getBoxAt(pos3d).stretch(velocity.x, velocity.y, velocity.z).expand(1.0D), + dimensions.getBoxAt(prevPos3d).stretch(velocity.x, velocity.y, velocity.z).expand(1.0D), entity -> !entity.isSpectator() && entity.isAlive() && entity.canHit(), ProjectileUtil.getToleranceMargin(simulatingEntity) ); From 516c2ab2248743771f29ce27016262795387428b Mon Sep 17 00:00:00 2001 From: Wide_Cat Date: Sun, 28 Dec 2025 17:42:17 +0000 Subject: [PATCH 4/6] further increase simulation accuracy, fix bugs, simulate projectiles piercing and deflecting --- .../systems/modules/combat/ArrowDodge.java | 4 +- .../systems/modules/render/Trajectories.java | 142 +++++++------- .../ProjectileEntitySimulator.java | 179 +++++++++++++----- .../entity/simulator/SimulationStep.java | 20 ++ .../resources/meteor-client.accesswidener | 2 + 5 files changed, 232 insertions(+), 115 deletions(-) rename src/main/java/meteordevelopment/meteorclient/utils/entity/{ => simulator}/ProjectileEntitySimulator.java (69%) create mode 100644 src/main/java/meteordevelopment/meteorclient/utils/entity/simulator/SimulationStep.java diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/ArrowDodge.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/ArrowDodge.java index e99744569d..322d0fa837 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/ArrowDodge.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/ArrowDodge.java @@ -9,7 +9,7 @@ import meteordevelopment.meteorclient.settings.*; import meteordevelopment.meteorclient.systems.modules.Categories; import meteordevelopment.meteorclient.systems.modules.Module; -import meteordevelopment.meteorclient.utils.entity.ProjectileEntitySimulator; +import meteordevelopment.meteorclient.utils.entity.simulator.ProjectileEntitySimulator; import meteordevelopment.meteorclient.utils.misc.Pool; import meteordevelopment.orbit.EventHandler; import net.minecraft.entity.Entity; @@ -119,7 +119,7 @@ private void onTick(TickEvent.Pre event) { if (!simulator.set(projectile)) continue; for (int i = 0; i < (simulationSteps.get() > 0 ? simulationSteps.get() : Integer.MAX_VALUE); i++) { points.add(vec3s.get().set(simulator.pos)); - if (simulator.tick() != null) break; + if (simulator.tick().shouldStop) break; } } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java index d99c1b22d9..24218fb3f0 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java @@ -11,7 +11,8 @@ import meteordevelopment.meteorclient.systems.modules.Categories; import meteordevelopment.meteorclient.systems.modules.Module; import meteordevelopment.meteorclient.utils.Utils; -import meteordevelopment.meteorclient.utils.entity.ProjectileEntitySimulator; +import meteordevelopment.meteorclient.utils.entity.simulator.ProjectileEntitySimulator; +import meteordevelopment.meteorclient.utils.entity.simulator.SimulationStep; import meteordevelopment.meteorclient.utils.misc.Pool; import meteordevelopment.meteorclient.utils.render.color.SettingColor; import meteordevelopment.orbit.EventHandler; @@ -89,7 +90,7 @@ public class Trajectories extends Module { private final Setting ignoreFirstTicks = sgRender.add(new IntSetting.Builder() .name("ignore-rendering-first-ticks") - .description("Ignores rendering the first ticks, to make the rest of the path more visible.") + .description("Ignores rendering the first given ticks, to make the rest of the path more visible.") .defaultValue(3) .min(0) .build() @@ -198,14 +199,14 @@ private void calculatePath(PlayerEntity player, float tickDelta) { // Calculate paths if (!simulator.set(player, itemStack, 0, accurate.get(), tickDelta)) return; - getEmptyPath().calculate(); + getEmptyPath().calculate().ignoreFirstTicks(); if (itemStack.getItem() instanceof CrossbowItem && Utils.hasEnchantment(itemStack, Enchantments.MULTISHOT)) { if (!simulator.set(player, itemStack, MULTISHOT_OFFSET, accurate.get(), tickDelta)) return; // left multishot arrow - getEmptyPath().calculate(); + getEmptyPath().calculate().ignoreFirstTicks(); if (!simulator.set(player, itemStack, -MULTISHOT_OFFSET, accurate.get(), tickDelta)) return; // right multishot arrow - getEmptyPath().calculate(); + getEmptyPath().calculate().ignoreFirstTicks(); } } @@ -232,7 +233,7 @@ private void onRender(Render3DEvent event) { for (Entity entity : mc.world.getEntities()) { if (entity instanceof ProjectileEntity) { if (ignoreWitherSkulls.get() && entity instanceof WitherSkullEntity) continue; - if (entity instanceof TridentEntity trident && trident.noClip) continue; + if (entity instanceof TridentEntity trident && trident.noClip) continue; // when it's returning via loyalty calculateFiredPath(entity, tickDelta); for (Path path : paths) path.render(event); @@ -247,31 +248,33 @@ private class Path { private boolean hitQuad, hitQuadHorizontal; private double hitQuadX1, hitQuadY1, hitQuadZ1, hitQuadX2, hitQuadY2, hitQuadZ2; - private Entity collidingEntity; + private final List collidingEntities = new ArrayList<>(); public Vector3d lastPoint; + private int start; public void clear() { vec3s.freeAll(points); points.clear(); hitQuad = false; - collidingEntity = null; + collidingEntities.clear(); lastPoint = null; + start = 0; } - public void calculate() { + public Path calculate() { addPoint(); for (int i = 0; i < (simulationSteps.get() > 0 ? simulationSteps.get() : Integer.MAX_VALUE); i++) { - HitResult result = simulator.tick(); + SimulationStep result = simulator.tick(); - if (result != null) { - processHitResult(result); - break; - } + processHitResults(result); + if (result.shouldStop) break; addPoint(); } + + return this; } public Path setStart(Entity entity, double tickDelta) { @@ -288,72 +291,75 @@ private void addPoint() { points.add(vec3s.get().set(simulator.pos)); } - private void processHitResult(HitResult result) { - if (result.getType() == HitResult.Type.BLOCK) { - BlockHitResult r = (BlockHitResult) result; - - hitQuad = true; - hitQuadX1 = r.getPos().x; - hitQuadY1 = r.getPos().y; - hitQuadZ1 = r.getPos().z; - hitQuadX2 = r.getPos().x; - hitQuadY2 = r.getPos().y; - hitQuadZ2 = r.getPos().z; - - if (r.getSide() == Direction.UP || r.getSide() == Direction.DOWN) { - hitQuadHorizontal = true; - hitQuadX1 -= 0.25; - hitQuadZ1 -= 0.25; - hitQuadX2 += 0.25; - hitQuadZ2 += 0.25; - } - else if (r.getSide() == Direction.NORTH || r.getSide() == Direction.SOUTH) { - hitQuadHorizontal = false; - hitQuadX1 -= 0.25; - hitQuadY1 -= 0.25; - hitQuadX2 += 0.25; - hitQuadY2 += 0.25; - } - else { - hitQuadHorizontal = false; - hitQuadZ1 -= 0.25; - hitQuadY1 -= 0.25; - hitQuadZ2 += 0.25; - hitQuadY2 += 0.25; + private void processHitResults(SimulationStep step) { + for (int i = 0; i < step.hitResults.length; i++) { + HitResult result = step.hitResults[i]; + if (result.getType() == HitResult.Type.BLOCK) { + BlockHitResult r = (BlockHitResult) result; + + hitQuad = true; + hitQuadX1 = r.getPos().x; + hitQuadY1 = r.getPos().y; + hitQuadZ1 = r.getPos().z; + hitQuadX2 = r.getPos().x; + hitQuadY2 = r.getPos().y; + hitQuadZ2 = r.getPos().z; + + if (r.getSide() == Direction.UP || r.getSide() == Direction.DOWN) { + hitQuadHorizontal = true; + hitQuadX1 -= 0.25; + hitQuadZ1 -= 0.25; + hitQuadX2 += 0.25; + hitQuadZ2 += 0.25; + } + else if (r.getSide() == Direction.NORTH || r.getSide() == Direction.SOUTH) { + hitQuadHorizontal = false; + hitQuadX1 -= 0.25; + hitQuadY1 -= 0.25; + hitQuadX2 += 0.25; + hitQuadY2 += 0.25; + } + else { + hitQuadHorizontal = false; + hitQuadZ1 -= 0.25; + hitQuadY1 -= 0.25; + hitQuadZ2 += 0.25; + hitQuadY2 += 0.25; + } + + points.add(Utils.set(vec3s.get(), result.getPos())); } + else if (result.getType() == HitResult.Type.ENTITY) { + Entity entity = ((EntityHitResult) result).getEntity(); + collidingEntities.add(entity); - points.add(Utils.set(vec3s.get(), result.getPos())); + if (step.shouldStop && i == step.hitResults.length - 1) { + points.add(Utils.set(vec3s.get(), result.getPos())); + } + } } - else if (result.getType() == HitResult.Type.ENTITY) { - collidingEntity = ((EntityHitResult) result).getEntity(); + } - points.add(Utils.set(vec3s.get(), result.getPos()).add(0, collidingEntity.getHeight() / 2, 0)); - } + public void ignoreFirstTicks() { + start = points.size() <= ignoreFirstTicks.get() ? 0 : ignoreFirstTicks.get(); } public void render(Render3DEvent event) { // Render path - - for (int i = (points.size() <= ignoreFirstTicks.get() ? 0 : ignoreFirstTicks.get()); i < points.size(); i++) { + for (int i = start; i < points.size(); i++) { Vector3d point = points.get(i); - if (lastPoint == null) { - if (i > 1) lastPoint = points.get(i - 1); - else { - lastPoint = point; - continue; + if (lastPoint != null) { + event.renderer.line(lastPoint.x, lastPoint.y, lastPoint.z, point.x, point.y, point.z, lineColor.get()); + if (renderPositionBox.get()) { + event.renderer.box( + point.x - positionBoxSize.get(), point.y - positionBoxSize.get(), point.z - positionBoxSize.get(), + point.x + positionBoxSize.get(), point.y + positionBoxSize.get(), point.z + positionBoxSize.get(), + positionSideColor.get(), positionLineColor.get(), shapeMode.get(), 0 + ); } } - event.renderer.line(lastPoint.x, lastPoint.y, lastPoint.z, point.x, point.y, point.z, lineColor.get()); - if (renderPositionBox.get()) { - event.renderer.box( - point.x - positionBoxSize.get(), point.y - positionBoxSize.get(), point.z - positionBoxSize.get(), - point.x + positionBoxSize.get(), point.y + positionBoxSize.get(), point.z + positionBoxSize.get(), - positionSideColor.get(), positionLineColor.get(), shapeMode.get(), 0 - ); - } - lastPoint = point; } @@ -364,7 +370,7 @@ public void render(Render3DEvent event) { } // Render entity - if (collidingEntity != null) { + for (Entity collidingEntity : collidingEntities) { double x = (collidingEntity.getX() - collidingEntity.lastX) * event.tickDelta; double y = (collidingEntity.getY() - collidingEntity.lastY) * event.tickDelta; double z = (collidingEntity.getZ() - collidingEntity.lastZ) * event.tickDelta; diff --git a/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java b/src/main/java/meteordevelopment/meteorclient/utils/entity/simulator/ProjectileEntitySimulator.java similarity index 69% rename from src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java rename to src/main/java/meteordevelopment/meteorclient/utils/entity/simulator/ProjectileEntitySimulator.java index a6e13cf70e..fb44f5d59a 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/entity/ProjectileEntitySimulator.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/entity/simulator/ProjectileEntitySimulator.java @@ -3,7 +3,7 @@ * Copyright (c) Meteor Development. */ -package meteordevelopment.meteorclient.utils.entity; +package meteordevelopment.meteorclient.utils.entity.simulator; import meteordevelopment.meteorclient.mixin.CrossbowItemAccessor; import meteordevelopment.meteorclient.mixin.ProjectileInGroundAccessor; @@ -12,23 +12,29 @@ import meteordevelopment.meteorclient.systems.modules.movement.NoSlow; import meteordevelopment.meteorclient.systems.modules.movement.Sneak; import meteordevelopment.meteorclient.utils.Utils; -import meteordevelopment.meteorclient.utils.misc.MissHitResult; import meteordevelopment.meteorclient.utils.player.Rotations; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.ChargedProjectilesComponent; +import net.minecraft.enchantment.Enchantments; import net.minecraft.entity.*; +import net.minecraft.entity.mob.BreezeEntity; import net.minecraft.entity.projectile.*; import net.minecraft.entity.projectile.thrown.*; import net.minecraft.fluid.FluidState; import net.minecraft.item.*; import net.minecraft.registry.tag.FluidTags; import net.minecraft.registry.tag.TagKey; +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.Quaterniond; import org.joml.Vector3d; +import java.util.ArrayList; +import java.util.Collection; + import static meteordevelopment.meteorclient.MeteorClient.mc; public class ProjectileEntitySimulator { @@ -40,9 +46,9 @@ public class ProjectileEntitySimulator { public final Vector3d pos = new Vector3d(); private final Vector3d velocity = new Vector3d(); - private Entity simulatingEntity; + private ProjectileEntity simulatingEntity; private EntityDimensions dimensions; - private int age; + private int age, pierceLevel; private double gravity; private float airDrag, waterDrag; private boolean isTouchingWater; @@ -103,7 +109,9 @@ public boolean set(Entity user, ItemStack itemStack, double angleOffset, boolean if (projectilesComponent.contains(Items.FIREWORK_ROCKET)) { set(user, angleOffset, accurate, tickDelta, FIREWORK_ROCKET.withPower(speed)); } - else set(user, angleOffset,accurate, tickDelta, ARROW.withPower(speed)); + else set(user, angleOffset, accurate, tickDelta, ARROW.withPower(speed)); + + this.pierceLevel = projectilesComponent.contains(Items.FIREWORK_ROCKET) ? 0 : Utils.getEnchantmentLevel(itemStack, Enchantments.PIERCING); } case WindChargeItem ignored -> set(user, angleOffset, accurate, tickDelta, WIND_CHARGE); case TridentItem ignored -> set(user, angleOffset, accurate, tickDelta, TRIDENT); @@ -148,15 +156,15 @@ public void set(Entity user, double angleOffset, boolean accurate, float tickDel z = Math.cos(yaw * 0.017453292) * Math.cos(pitch * 0.017453292); } else { - Vec3d vec3d = user.getOppositeRotationVector(1.0F); - Quaterniond quaternion = new Quaterniond().setAngleAxis(angleOffset, vec3d.x, vec3d.y, vec3d.z); - Vec3d vec3d2 = user.getRotationVec(1.0F); - Vector3d vector3f = new Vector3d(vec3d2.x, vec3d2.y, vec3d2.z); - vector3f.rotate(quaternion); - - x = vector3f.x; - y = vector3f.y; - z = vector3f.z; + Vec3d oppositeRotationVec = user.getOppositeRotationVector(1.0F); + Quaterniond quaternion = new Quaterniond().setAngleAxis(angleOffset, oppositeRotationVec.x, oppositeRotationVec.y, oppositeRotationVec.z); + Vec3d rotationVec = user.getRotationVec(1.0F); + Vector3d vector3d = new Vector3d(rotationVec.x, rotationVec.y, rotationVec.z); + vector3d.rotate(quaternion); + + x = vector3d.x; + y = vector3d.y; + z = vector3d.z; } velocity.set(x, y, z).normalize().mul(data.power()); @@ -166,7 +174,7 @@ public void set(Entity user, double angleOffset, boolean accurate, float tickDel velocity.add(vel.x, user.isOnGround() ? 0.0D : vel.y, vel.z); } - setSimulationData(data.entity().create(mc.world, null), data); + setSimulationData((ProjectileEntity) data.entity().create(mc.world, null), data); } public void setFishingBobber(Entity user, float tickDelta, MotionData data) { @@ -195,7 +203,7 @@ public void setFishingBobber(Entity user, float tickDelta, MotionData data) { double l = velocity.length(); velocity.mul(0.6 / l + 0.5, 0.6 / l + 0.5, 0.6 / l + 0.5); - setSimulationData(data.entity().create(mc.world, null), data); + setSimulationData((ProjectileEntity) data.entity().create(mc.world, null), data); } @@ -230,7 +238,7 @@ public boolean set(Entity entity) { return true; } - public void set(Entity entity, MotionData data) { + public void set(ProjectileEntity entity, MotionData data) { pos.set(entity.getX(), entity.getY(), entity.getZ()); double speed = entity.getVelocity().length(); @@ -239,7 +247,7 @@ public void set(Entity entity, MotionData data) { setSimulationData(entity, data); } - private void setSimulationData(Entity entity, MotionData data) { + private void setSimulationData(ProjectileEntity entity, MotionData data) { this.gravity = data.gravity(); this.airDrag = data.airDrag(); this.waterDrag = data.waterDrag(); @@ -247,9 +255,10 @@ private void setSimulationData(Entity entity, MotionData data) { this.dimensions = simulatingEntity.getDimensions(simulatingEntity.getPose()); this.isTouchingWater = simulatingEntity.isTouchingWater(); this.age = simulatingEntity.age; + this.pierceLevel = 0; } - public HitResult tick() { + public SimulationStep tick() { age++; ((IVec3d) prevPos3d).meteor$set(pos); @@ -277,19 +286,18 @@ else if (simulatingEntity instanceof ProjectileEntity) { } // Check if below world - if (pos.y < mc.world.getBottomY()) return MissHitResult.INSTANCE; + if (pos.y < mc.world.getBottomY()) return SimulationStep.MISS; // Check if chunk is loaded int chunkX = ChunkSectionPos.getSectionCoord(pos.x); int chunkZ = ChunkSectionPos.getSectionCoord(pos.z); - if (!mc.world.getChunkManager().isChunkLoaded(chunkX, chunkZ)) return MissHitResult.INSTANCE; + if (!mc.world.getChunkManager().isChunkLoaded(chunkX, chunkZ)) return SimulationStep.MISS; // Check for collision ((IVec3d) pos3d).meteor$set(pos); - if (pos3d.equals(prevPos3d)) return MissHitResult.INSTANCE; + if (pos3d.equals(prevPos3d)) return SimulationStep.MISS; - HitResult hitResult = getCollision(); - return hitResult.getType() == HitResult.Type.MISS ? null : hitResult; + return getCollision(); } /** @@ -325,42 +333,123 @@ public void tickIsTouchingWater() { /** * {@link ProjectileUtil#getCollision(Vec3d, Entity, java.util.function.Predicate, Vec3d, net.minecraft.world.World, float, RaycastContext.ShapeType)} - * + *

* Vanilla checks from the current to the next position, while we check from the previous to the current positions. * This solves the issue of the collision check from the starting position not working properly - otherwise, the * simulated projectile may move from its start position through a block, only running the collision check afterwards. * The vanilla game has other code to deal with this but this is the easiest way for us to fix it. */ - private HitResult getCollision() { - HitResult hitResult = mc.world.getCollisionsIncludingWorldBorder(new RaycastContext( + private SimulationStep getCollision() { + HitResult blockCollision = mc.world.getCollisionsIncludingWorldBorder(new RaycastContext( prevPos3d, pos3d, RaycastContext.ShapeType.COLLIDER, waterDrag == 0 ? RaycastContext.FluidHandling.ANY : RaycastContext.FluidHandling.NONE, simulatingEntity )); - if (hitResult.getType() != HitResult.Type.MISS) { - ((IVec3d) pos3d).meteor$set(hitResult.getPos()); + if (blockCollision.getType() != HitResult.Type.MISS) { + ((IVec3d) pos3d).meteor$set(blockCollision.getPos()); } - // When entities are spawned, they first move and then check collisions against their current and next positions. - // Since we move and then check from the previous to the current position, it can sometimes detect thrown - // projectiles as colliding against ourselves, which does not happen for the first tick of movement. - if (age <= 1) return hitResult; + /** {@link PersistentProjectileEntity#applyCollision(BlockHitResult)} */ + if (simulatingEntity instanceof PersistentProjectileEntity) { + Collection entityCollisions = ProjectileUtil.collectPiercingCollisions( + mc.world, + simulatingEntity, + prevPos3d, + pos3d, + dimensions.getBoxAt(prevPos3d).stretch(velocity.x, velocity.y, velocity.z).expand(1.0D), + entity -> !entity.isSpectator() && entity.isAlive() && entity.canHit(), + getToleranceMargin(), + RaycastContext.ShapeType.COLLIDER, + false + ); + + // prevent simulating projectiles as colliding with ourselves on the first tick of movement + entityCollisions.removeIf(collision -> age <= 1 && collision.getEntity() == mc.player); + if (entityCollisions.isEmpty()) return new SimulationStep(hitOrDeflect(blockCollision), blockCollision); + + boolean stop = false; + ArrayList hits = new ArrayList<>(); + for (EntityHitResult result : entityCollisions) { + boolean hit = hitOrDeflect(result); + if (!hit) break; + + hits.add(result); + if (pierceLevel <= 0) { + stop = true; + break; + } - HitResult hitResult2 = ProjectileUtil.getEntityCollision( - mc.world, - simulatingEntity, - prevPos3d, - pos3d, - dimensions.getBoxAt(prevPos3d).stretch(velocity.x, velocity.y, velocity.z).expand(1.0D), - entity -> !entity.isSpectator() && entity.isAlive() && entity.canHit(), - ProjectileUtil.getToleranceMargin(simulatingEntity) - ); - if (hitResult2 != null) { - hitResult = hitResult2; + pierceLevel--; + } + + return new SimulationStep(stop, hits.toArray(new HitResult[0])); + } + else { + HitResult entityCollision = ProjectileUtil.getEntityCollision( + mc.world, + simulatingEntity, + prevPos3d, + pos3d, + dimensions.getBoxAt(prevPos3d).stretch(velocity.x, velocity.y, velocity.z).expand(1.0D), + entity -> !entity.isSpectator() && entity.isAlive() && entity.canHit(), + getToleranceMargin() + ); + + if (entityCollision == null || (age <= 1 && entityCollision instanceof EntityHitResult ehr && ehr.getEntity() == mc.player)) { + return new SimulationStep(hitOrDeflect(blockCollision), blockCollision); + } + + if (hitOrDeflect(entityCollision)) return new SimulationStep(true, entityCollision); + return new SimulationStep(false); + } + } + + /** + * {@link ProjectileEntity#hitOrDeflect(HitResult)} + * {@link ProjectileEntity#onCollision(HitResult)} + * {@link ProjectileDeflection} + */ + private boolean hitOrDeflect(HitResult hitResult) { + if (hitResult instanceof EntityHitResult entityHitResult) { + Entity entity = entityHitResult.getEntity(); + Utils.set(pos, entityHitResult.getPos()); + + if ((entity instanceof BreezeEntity && !(simulatingEntity instanceof AbstractWindChargeEntity)) || entity.getProjectileDeflection(simulatingEntity) == ProjectileDeflection.SIMPLE) { + velocity.mul(-0.5); + return false; + } + + // if we keep this it makes trajectories look awful when you throw wind charges +// if (entity.getType().isIn(EntityTypeTags.REDIRECTABLE_PROJECTILE) && entity instanceof ProjectileEntity projectileEntity) { +// Utils.set(velocity, projectileEntity.getRotationVector()); +// return false; +// } + + // not perfectly accurate but you would otherwise have to trace significant amounts of the damage stack + if (entity instanceof LivingEntity livingEntity && livingEntity.isBlocking() && simulatingEntity instanceof PersistentProjectileEntity) { + velocity.mul(-0.5).mul(0.2); + return velocity.lengthSquared() < 1.0E-7; + } + + return true; } + else if (hitResult instanceof BlockHitResult bhr) { + Utils.set(pos, bhr.getPos()); + + if (simulatingEntity.deflectsAgainstWorldBorder() && bhr.isAgainstWorldBorder()) { + velocity.mul(-0.5).mul(0.2); + return false; + } + + return bhr.getType() != HitResult.Type.MISS; + } + + return false; + } - return hitResult; + private float getToleranceMargin() { + return Math.max(0.0F, Math.min(0.3F, (age - 2) / 20.0F)); } } diff --git a/src/main/java/meteordevelopment/meteorclient/utils/entity/simulator/SimulationStep.java b/src/main/java/meteordevelopment/meteorclient/utils/entity/simulator/SimulationStep.java new file mode 100644 index 0000000000..af46e70dbb --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/utils/entity/simulator/SimulationStep.java @@ -0,0 +1,20 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.utils.entity.simulator; + +import net.minecraft.util.hit.HitResult; + +public class SimulationStep { + public static final SimulationStep MISS = new SimulationStep(true); + + public boolean shouldStop; + public HitResult[] hitResults; + + public SimulationStep(boolean stop, HitResult... hitResults) { + this.shouldStop = stop; + this.hitResults = hitResults; + } +} diff --git a/src/main/resources/meteor-client.accesswidener b/src/main/resources/meteor-client.accesswidener index 35e17dc785..996e32cc19 100644 --- a/src/main/resources/meteor-client.accesswidener +++ b/src/main/resources/meteor-client.accesswidener @@ -30,3 +30,5 @@ accessible method net/minecraft/client/network/ClientPlayerInteractionManager accessible class net/minecraft/client/gui/DrawContext$ScissorStack accessible field net/minecraft/client/render/RenderSetup outputTarget Lnet/minecraft/client/render/OutputTarget; + +accessible method net/minecraft/entity/projectile/ProjectileEntity deflectsAgainstWorldBorder ()Z From 67425fc19fd27a265114469e0384d3e583fba4e0 Mon Sep 17 00:00:00 2001 From: Wide_Cat Date: Sun, 28 Dec 2025 21:47:43 +0000 Subject: [PATCH 5/6] make the ignore first ticks setting work only on yourself --- .../systems/modules/render/Trajectories.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java index 24218fb3f0..8d5077999a 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/Trajectories.java @@ -199,14 +199,17 @@ private void calculatePath(PlayerEntity player, float tickDelta) { // Calculate paths if (!simulator.set(player, itemStack, 0, accurate.get(), tickDelta)) return; - getEmptyPath().calculate().ignoreFirstTicks(); + Path p = getEmptyPath().calculate(); + if (player == mc.player) p.ignoreFirstTicks(); if (itemStack.getItem() instanceof CrossbowItem && Utils.hasEnchantment(itemStack, Enchantments.MULTISHOT)) { if (!simulator.set(player, itemStack, MULTISHOT_OFFSET, accurate.get(), tickDelta)) return; // left multishot arrow - getEmptyPath().calculate().ignoreFirstTicks(); + p = getEmptyPath().calculate(); + if (player == mc.player) p.ignoreFirstTicks(); if (!simulator.set(player, itemStack, -MULTISHOT_OFFSET, accurate.get(), tickDelta)) return; // right multishot arrow - getEmptyPath().calculate().ignoreFirstTicks(); + p = getEmptyPath().calculate(); + if (player == mc.player) p.ignoreFirstTicks(); } } From 991b719e0238f782e176eda633fb53c41d6ee543 Mon Sep 17 00:00:00 2001 From: Wide_Cat Date: Sun, 28 Dec 2025 22:26:20 +0000 Subject: [PATCH 6/6] h --- .../utils/entity/simulator/ProjectileEntitySimulator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/meteordevelopment/meteorclient/utils/entity/simulator/ProjectileEntitySimulator.java b/src/main/java/meteordevelopment/meteorclient/utils/entity/simulator/ProjectileEntitySimulator.java index fb44f5d59a..0c125b7e6c 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/entity/simulator/ProjectileEntitySimulator.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/entity/simulator/ProjectileEntitySimulator.java @@ -277,7 +277,7 @@ else if (simulatingEntity instanceof PersistentProjectileEntity || simulatingEnt tickIsTouchingWater(); } // gravity -> position > drag - // accurate for fishing bobbers and firework rockets + // accurate for fishing bobbers and firework rockets, will need to revisit if more projectiles are added else if (simulatingEntity instanceof ProjectileEntity) { tickIsTouchingWater(); velocity.sub(0, gravity, 0);