diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/AutoCity.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/AutoCity.java index 945fcb6a23..cced7abc57 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/AutoCity.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/combat/AutoCity.java @@ -11,6 +11,8 @@ import meteordevelopment.meteorclient.settings.*; import meteordevelopment.meteorclient.systems.modules.Categories; import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.systems.modules.Modules; +import meteordevelopment.meteorclient.utils.Utils; import meteordevelopment.meteorclient.utils.entity.EntityUtils; import meteordevelopment.meteorclient.utils.entity.SortPriority; import meteordevelopment.meteorclient.utils.entity.TargetUtils; @@ -23,17 +25,27 @@ import meteordevelopment.orbit.EventHandler; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Items; +import net.minecraft.block.Block; import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket; import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket; import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.world.RaycastContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Comparator; public class AutoCity extends Module { private final SettingGroup sgGeneral = settings.getDefaultGroup(); + private final SettingGroup sgBreak = settings.createGroup("Break"); + private final SettingGroup sgSupport = settings.createGroup("Support"); + private final SettingGroup sgPause = settings.createGroup("Pause"); private final SettingGroup sgRender = settings.createGroup("Render"); - private final Setting targetRange = sgGeneral.add(new DoubleSetting.Builder() .name("target-range") .description("The radius in which players get targeted.") @@ -43,32 +55,88 @@ public class AutoCity extends Module { .build() ); - private final Setting breakRange = sgGeneral.add(new DoubleSetting.Builder() + private final Setting priority = sgGeneral.add(new EnumSetting.Builder() + .name("target-priority") + .description("How to filter targets within range.") + .defaultValue(SortPriority.LowestDistance) + .build() + ); + + private final Setting autoSwitch = sgGeneral.add(new BoolSetting.Builder() + .name("auto-switch") + .description("Switches to your pickaxe automatically.") + .defaultValue(true) + .build() + ); + + private final Setting swapBack = sgGeneral.add(new BoolSetting.Builder() + .name("swap-back") + .description("Switches to your previous slot after mining.") + .defaultValue(true) + .visible(autoSwitch::get) + .build() + ); + + private final Setting rotate = sgGeneral.add(new BoolSetting.Builder() + .name("rotate") + .description("Rotates server-side towards the block being placed/broken.") + .defaultValue(true) + .build() + ); + + // Break + + private final Setting breakRange = sgBreak.add(new DoubleSetting.Builder() .name("break-range") - .description("How close a block must be to you to be considered.") + .description("Range in which to break blocks.") .defaultValue(4.5) .min(0) .sliderMax(6) .build() ); - private final Setting switchMode = sgGeneral.add(new EnumSetting.Builder() - .name("switch-mode") - .description("How to switch to a pickaxe.") - .defaultValue(SwitchMode.Normal) + private final Setting breakWallsRange = sgBreak.add(new DoubleSetting.Builder() + .name("walls-range") + .description("Range in which to break when behind blocks.") + .defaultValue(4.5) + .min(0) + .sliderMax(6) + .build() + ); + + private final Setting packetMine = sgBreak.add(new BoolSetting.Builder() + .name("packet-mine") + .description("Sends packets to the block that is being broken.") + .defaultValue(true) + .build() + ); + + private final Setting safeBreak = sgBreak.add(new BoolSetting.Builder() + .name("safe-break") + .description("Prevents breaking blocks that protect you from explosions.") + .defaultValue(true) + .build() + ); + + private final Setting sortMode = sgBreak.add(new EnumSetting.Builder() + .name("sort-mode") + .description("The blocks you want to break first.") + .defaultValue(SortMode.Furthest) .build() ); - private final Setting support = sgGeneral.add(new BoolSetting.Builder() + // Support + + private final Setting support = sgSupport.add(new BoolSetting.Builder() .name("support") - .description("If there is no block below a city block it will place one before mining.") + .description("Places obsidian under blocks that have been mined.") .defaultValue(true) .build() ); - private final Setting placeRange = sgGeneral.add(new DoubleSetting.Builder() + private final Setting placeRange = sgSupport.add(new DoubleSetting.Builder() .name("place-range") - .description("How far away to try and place a block.") + .description("The range at which the support block can be placed.") .defaultValue(4.5) .min(0) .sliderMax(6) @@ -76,32 +144,44 @@ public class AutoCity extends Module { .build() ); - private final Setting rotate = sgGeneral.add(new BoolSetting.Builder() - .name("rotate") - .description("Automatically rotates you towards the city block.") + private final Setting placeWallsRange = sgSupport.add(new DoubleSetting.Builder() + .name("walls-range") + .description("Range in which to place the support block when behind blocks.") + .defaultValue(4.5) + .min(0) + .sliderMax(6) + .visible(support::get) + .build() + ); + + // Pause + + private final Setting pauseOnUse = sgPause.add(new BoolSetting.Builder() + .name("pause-on-use") + .description("Does not break blocks while using an item.") .defaultValue(true) .build() ); - private final Setting chatInfo = sgGeneral.add(new BoolSetting.Builder() - .name("chat-info") - .description("Whether the module should send messages in chat.") + private final Setting pauseOnCA = sgPause.add(new BoolSetting.Builder() + .name("pause-on-CA") + .description("Does not break blocks while Crystal Aura is placing.") .defaultValue(true) .build() ); // Render - private final Setting swingHand = sgRender.add(new BoolSetting.Builder() - .name("swing-hand") - .description("Whether to render your hand swinging.") + private final Setting swing = sgRender.add(new BoolSetting.Builder() + .name("swing") + .description("Whether to swing hand client-side.") .defaultValue(false) .build() ); - private final Setting renderBlock = sgRender.add(new BoolSetting.Builder() - .name("render-block") - .description("Whether to render the block being broken.") + private final Setting render = sgRender.add(new BoolSetting.Builder() + .name("render") + .description("Renders the block that Auto City is breaking.") .defaultValue(true) .build() ); @@ -110,15 +190,15 @@ public class AutoCity extends Module { .name("shape-mode") .description("How the shapes are rendered.") .defaultValue(ShapeMode.Both) - .visible(renderBlock::get) + .visible(render::get) .build() ); private final Setting sideColor = sgRender.add(new ColorSetting.Builder() .name("side-color") .description("The side color of the rendering.") - .defaultValue(new SettingColor(225, 0, 0, 75)) - .visible(() -> renderBlock.get() && shapeMode.get().sides()) + .defaultValue(new SettingColor(225, 0, 0, 65)) + .visible(() -> render.get() && shapeMode.get().sides()) .build() ); @@ -126,105 +206,211 @@ public class AutoCity extends Module { .name("line-color") .description("The line color of the rendering.") .defaultValue(new SettingColor(225, 0, 0, 255)) - .visible(() -> renderBlock.get() && shapeMode.get().lines()) + .visible(() -> render.get() && shapeMode.get().lines()) .build() ); private PlayerEntity target; - private BlockPos targetPos; - private FindItemResult pick; - private float progress; + public BlockPos breakPos; + private double progress; + private Block lastBlock; + private boolean mining; public AutoCity() { super(Categories.Combat, "auto-city", "Automatically mine blocks next to someone's feet."); } @Override - public void onActivate() { - target = TargetUtils.getPlayerTarget(targetRange.get(), SortPriority.ClosestAngle); - if (TargetUtils.isBadTarget(target, targetRange.get())) { - if (chatInfo.get()) error("Couldn't find a target, disabling."); + public void onDeactivate() { + breakPos = null; + progress = 0; + mining = false; + } + + @EventHandler + private void onTick(TickEvent.Pre event) { + FindItemResult tool = InvUtils.findInHotbar(itemStack -> itemStack.getItem() == Items.DIAMOND_PICKAXE || itemStack.getItem() == Items.NETHERITE_PICKAXE); + if (!tool.isHotbar()) { + error("No pickaxe found... disabling."); toggle(); return; } - targetPos = EntityUtils.getCityBlock(target); - if (targetPos == null || PlayerUtils.squaredDistanceTo(targetPos) > Math.pow(breakRange.get(), 2)) { - if (chatInfo.get()) error("Couldn't find a good block, disabling."); - toggle(); - return; + // Pause + if (shouldPause()) return; + + // Find target + if (TargetUtils.isBadTarget(target, targetRange.get())) { + onDeactivate(); + target = TargetUtils.getPlayerTarget(targetRange.get(), priority.get()); + if (TargetUtils.isBadTarget(target, targetRange.get())) return; + } + + // Attempt to place a support block under any recently broken block, if applicable + BlockPos oldBreakPos = breakPos; + if (support.get() && oldBreakPos != null && mc.world.getBlockState(oldBreakPos).getBlock() != lastBlock){ + placeSupport(oldBreakPos.down()); + } + + breakPos = getBlockToMine(target); + if (breakPos == null) return; + + // Reset break progress if block changed + Block block = mc.world.getBlockState(breakPos).getBlock(); + if (breakPos != oldBreakPos || block != lastBlock) { + lastBlock = block; + progress = 0; + mining = false; } - if (support.get()) { - BlockPos supportPos = targetPos.down(); - if (!(PlayerUtils.squaredDistanceTo(supportPos) > Math.pow(placeRange.get(), 2))) { - BlockUtils.place(supportPos, InvUtils.findInHotbar(Items.OBSIDIAN), rotate.get(), 0, true); + breakBlock(tool); + } + + private void breakBlock(FindItemResult tool) { + boolean hasPickaxe = autoSwitch.get() || mc.player.getInventory().getSelectedSlot() == tool.slot(); + + // Packet mining mode + if (packetMine.get()) { + boolean start = progress == 0 && !mining; + + // Packets are only sent twice- at the beginning and end of the mining process + if ((start || progress >= 1) && hasPickaxe) { + if (rotate.get()) Rotations.rotate(Rotations.getYaw(breakPos), Rotations.getPitch(breakPos), () -> packetMineBlock(tool, start)); + else packetMineBlock(tool, start); + } + + if (mining && progress < 1) { + progress += BlockUtils.getBreakDelta(tool.slot(), mc.world.getBlockState(breakPos)); + } + // Legit mining mode + } else { + if (hasPickaxe) { + if (rotate.get()) Rotations.rotate(Rotations.getYaw(breakPos), Rotations.getPitch(breakPos), () -> mineBlock(tool)); + else mineBlock(tool); } } + } - pick = InvUtils.find(itemStack -> itemStack.getItem() == Items.DIAMOND_PICKAXE || itemStack.getItem() == Items.NETHERITE_PICKAXE); - if (!pick.isHotbar()) { - error("No pickaxe found... disabling."); - toggle(); - return; + private void packetMineBlock(FindItemResult tool, boolean start) { + if (autoSwitch.get()) InvUtils.swap(tool.slot(), swapBack.get()); + + Direction direction = BlockUtils.getDirection(breakPos); + if (start) { + // Begin mining the block + mc.getNetworkHandler().sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, breakPos, direction)); + mining = true; + } else { + // Finish mining the block + mc.getNetworkHandler().sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.STOP_DESTROY_BLOCK, breakPos, direction)); + progress = 0; + mining = false; } - progress = 0.0f; - mine(false); + if (swing.get()) mc.player.swingHand(Hand.MAIN_HAND); + else mc.getNetworkHandler().sendPacket(new HandSwingC2SPacket(Hand.MAIN_HAND)); + + if (swapBack.get()) InvUtils.swapBack(); } - @Override - public void onDeactivate() { - target = null; - targetPos = null; + private void mineBlock(FindItemResult tool) { + if (autoSwitch.get()) InvUtils.swap(tool.slot(), swapBack.get()); + + BlockUtils.breakBlock(breakPos, swing.get()); + + if (swapBack.get()) InvUtils.swapBack(); } - @EventHandler - private void onTick(TickEvent.Pre event) { - if (TargetUtils.isBadTarget(target, targetRange.get())) { - toggle(); - return; + public BlockPos getBlockToMine(PlayerEntity player) { + if (breakPos != null && canMineBlock(breakPos)) return breakPos; + + // Burrow block is first priority to break + BlockPos targetPos = player.getBlockPos(); + if (canMineBlock(targetPos)) return targetPos; + + // Otherwise, break their surround blocks + List blocks = new ArrayList<>(); + for (Direction direction : Direction.HORIZONTAL) { + BlockPos neighborPos = targetPos.offset(direction); + if (canMineBlock(neighborPos) && !isRiskyBreak(neighborPos)) { + blocks.add(neighborPos); + } } - if (PlayerUtils.squaredDistanceTo(targetPos) > Math.pow(breakRange.get(), 2)) { - if (chatInfo.get()) error("Couldn't find a target, disabling."); - toggle(); - return; + if (blocks.isEmpty()) return null; + + // Sort blocks + if (sortMode.get() != SortMode.None) { + double x = mc.player.getX(), y = mc.player.getY(), z = mc.player.getZ(); + blocks.sort(Comparator.comparingDouble(value -> Utils.squaredDistance(x, y, z, value.getX() + 0.5, value.getY() + 0.5, value.getZ() + 0.5) * (sortMode.get() == SortMode.Closest ? -1 : 1))); } - if (progress < 1.0f) { - pick = InvUtils.find(itemStack -> itemStack.getItem() == Items.DIAMOND_PICKAXE || itemStack.getItem() == Items.NETHERITE_PICKAXE); - if (!pick.isHotbar()) { - error("No pickaxe found... disabling."); - toggle(); - return; - } - progress += BlockUtils.getBreakDelta(pick.slot(), mc.world.getBlockState(targetPos)); - if (progress < 1.0f) return; + return blocks.getLast(); + } + + private boolean canMineBlock(BlockPos blockPos) { + // Block must be explosive resistant but breakable + Block block = mc.world.getBlockState(blockPos).getBlock(); + if (block.getBlastResistance() < 600 || block.getHardness() < 0) return false; + + // Check range and raycast + return !isOutOfRange(blockPos, breakRange.get(), breakWallsRange.get()); + } + + private boolean isRiskyBreak(BlockPos blockPos) { + if (!safeBreak.get()) return false; + + BlockPos myBlockPos = mc.player.getBlockPos(); + + // If we are burrowed, the only risky break is our own burrow block + Block block = mc.world.getBlockState(myBlockPos).getBlock(); + if (block.getBlastResistance() >= 600) return myBlockPos.equals(blockPos); + + // Otherwise, make certain we arent breaking our own surround blocks + for (Direction direction : Direction.HORIZONTAL) { + BlockPos neighborPos = myBlockPos.offset(direction); + if (neighborPos.equals(blockPos)) return true; } - mine(true); - toggle(); + return false; } - public void mine(boolean done) { - InvUtils.swap(pick.slot(), switchMode.get() == SwitchMode.Silent); - if (rotate.get()) Rotations.rotate(Rotations.getYaw(targetPos), Rotations.getPitch(targetPos)); + private boolean isOutOfRange(BlockPos blockPos, double baseRange, double wallsRange) { + Vec3d pos = blockPos.toCenterPos(); + if (!PlayerUtils.isWithin(pos, baseRange)) return true; - Direction direction = BlockUtils.getDirection(targetPos); - if (!done) mc.getNetworkHandler().sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, targetPos, direction)); - mc.getNetworkHandler().sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.STOP_DESTROY_BLOCK, targetPos, direction)); + RaycastContext raycastContext = new RaycastContext(mc.player.getEyePos(), pos, RaycastContext.ShapeType.COLLIDER, RaycastContext.FluidHandling.NONE, mc.player); + BlockHitResult result = mc.world.raycast(raycastContext); + if (result == null || !result.getBlockPos().equals(blockPos)) + return !PlayerUtils.isWithin(pos, wallsRange); - if (swingHand.get()) mc.player.swingHand(Hand.MAIN_HAND); - else mc.getNetworkHandler().sendPacket(new HandSwingC2SPacket(Hand.MAIN_HAND)); + return false; + } + + private void placeSupport(BlockPos blockPos) { + FindItemResult item = InvUtils.findInHotbar(Items.OBSIDIAN); + if (!item.found()) return; + + if (!BlockUtils.canPlace(blockPos)) return; + + // Check range and raycast + if (isOutOfRange(blockPos, placeRange.get(), placeWallsRange.get())) return; + + BlockUtils.place(blockPos, item, rotate.get(), 40, swing.get()); + } + + private boolean shouldPause() { + if (pauseOnUse.get() && mc.player.isUsingItem()) return true; + + CrystalAura CA = Modules.get().get(CrystalAura.class); + if (pauseOnCA.get() && CA.isActive() && CA.kaTimer > 0) return true; - if (switchMode.get() == SwitchMode.Silent) InvUtils.swapBack(); + return false; } @EventHandler private void onRender3D(Render3DEvent event) { - if (targetPos == null || !renderBlock.get()) return; - event.renderer.box(targetPos, sideColor.get(), lineColor.get(), shapeMode.get(), 0); + if (!render.get() || breakPos == null) return; + event.renderer.box(breakPos, sideColor.get(), lineColor.get(), shapeMode.get(), 0); } @Override @@ -232,8 +418,9 @@ public String getInfoString() { return EntityUtils.getName(target); } - public enum SwitchMode { - Normal, - Silent + public enum SortMode { + None, + Closest, + Furthest } } diff --git a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/CityESP.java b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/CityESP.java index 6d9816db96..46fed8b686 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/modules/render/CityESP.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/modules/render/CityESP.java @@ -14,7 +14,8 @@ import meteordevelopment.meteorclient.settings.SettingGroup; import meteordevelopment.meteorclient.systems.modules.Categories; import meteordevelopment.meteorclient.systems.modules.Module; -import meteordevelopment.meteorclient.utils.entity.EntityUtils; +import meteordevelopment.meteorclient.systems.modules.Modules; +import meteordevelopment.meteorclient.systems.modules.combat.AutoCity; import meteordevelopment.meteorclient.utils.entity.SortPriority; import meteordevelopment.meteorclient.utils.entity.TargetUtils; import meteordevelopment.meteorclient.utils.render.color.SettingColor; @@ -48,7 +49,7 @@ public class CityESP extends Module { .build() ); - private BlockPos target; + private BlockPos cityPos; public CityESP() { super(Categories.Render, "city-esp", "Displays blocks that can be broken in order to city another player."); @@ -56,19 +57,20 @@ public CityESP() { @EventHandler private void onTick(TickEvent.Post event) { - PlayerEntity targetEntity = TargetUtils.getPlayerTarget(mc.player.getBlockInteractionRange() + 2, SortPriority.LowestDistance); + cityPos = null; - if (TargetUtils.isBadTarget(targetEntity, mc.player.getBlockInteractionRange() + 2)) { - target = null; - } else { - target = EntityUtils.getCityBlock(targetEntity); + PlayerEntity target = TargetUtils.getPlayerTarget(mc.player.getBlockInteractionRange() + 2, SortPriority.LowestDistance); + if (TargetUtils.isBadTarget(target, mc.player.getBlockInteractionRange() + 2)) return; + + AutoCity autoCity = Modules.get().get(AutoCity.class); + if (autoCity.breakPos == null) { + cityPos = autoCity.getBlockToMine(target); } } @EventHandler private void onRender(Render3DEvent event) { - if (target == null) return; - - event.renderer.box(target, sideColor.get(), lineColor.get(), shapeMode.get(), 0); + if (cityPos == null) return; + event.renderer.box(cityPos, sideColor.get(), lineColor.get(), shapeMode.get(), 0); } } diff --git a/src/main/java/meteordevelopment/meteorclient/utils/entity/EntityUtils.java b/src/main/java/meteordevelopment/meteorclient/utils/entity/EntityUtils.java index 4358f4e81c..b7dbf04d92 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/entity/EntityUtils.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/entity/EntityUtils.java @@ -14,9 +14,7 @@ import meteordevelopment.meteorclient.mixin.WorldAccessor; import meteordevelopment.meteorclient.utils.player.PlayerUtils; import meteordevelopment.meteorclient.utils.render.color.Color; -import net.minecraft.block.Block; import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; import net.minecraft.block.entity.BlockEntity; import net.minecraft.client.network.PlayerListEntry; import net.minecraft.entity.Entity; @@ -30,7 +28,6 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Box; import net.minecraft.util.math.ChunkSectionPos; -import net.minecraft.util.math.Direction; import net.minecraft.world.GameMode; import net.minecraft.world.entity.EntityLookup; import net.minecraft.world.entity.EntityTrackingSection; @@ -43,8 +40,6 @@ import static meteordevelopment.meteorclient.MeteorClient.mc; public class EntityUtils { - private static final BlockPos.Mutable testPos = new BlockPos.Mutable(); - private EntityUtils() { } @@ -118,30 +113,6 @@ public static boolean isInRenderDistance(double posX, double posZ) { return x < d && z < d; } - public static BlockPos getCityBlock(PlayerEntity player) { - if (player == null) return null; - - double bestDistanceSquared = 6 * 6; - Direction bestDirection = null; - - for (Direction direction : Direction.HORIZONTAL) { - testPos.set(player.getBlockPos().offset(direction)); - - Block block = mc.world.getBlockState(testPos).getBlock(); - if (block != Blocks.OBSIDIAN && block != Blocks.NETHERITE_BLOCK && block != Blocks.CRYING_OBSIDIAN - && block != Blocks.RESPAWN_ANCHOR && block != Blocks.ANCIENT_DEBRIS) continue; - - double testDistanceSquared = PlayerUtils.squaredDistanceTo(testPos); - if (testDistanceSquared < bestDistanceSquared) { - bestDistanceSquared = testDistanceSquared; - bestDirection = direction; - } - } - - if (bestDirection == null) return null; - return player.getBlockPos().offset(bestDirection); - } - public static String getName(Entity entity) { if (entity == null) return null; if (entity instanceof PlayerEntity) return entity.getName().getString();