diff --git a/.gitignore b/.gitignore index 09cd281f..42eab877 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ bin/ # fabric run/ + +/temp \ No newline at end of file diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/com/nnpg/glazed/GlazedAddon.java b/src/main/java/com/nnpg/glazed/GlazedAddon.java index de3d0a99..cb430181 100644 --- a/src/main/java/com/nnpg/glazed/GlazedAddon.java +++ b/src/main/java/com/nnpg/glazed/GlazedAddon.java @@ -101,6 +101,9 @@ public void onInitialize() { Modules.get().add(new PremiumTunnelBaseFinder()); Modules.get().add(new AdminList()); Modules.get().add(new AutoTreeFarmer()); + Modules.get().add(new LayerLock()); + Modules.get().add(new ItemESP()); + Modules.get().add(new FastXP()); } @EventHandler diff --git a/src/main/java/com/nnpg/glazed/mixins/MixinClientPlayerInteractionManager.java b/src/main/java/com/nnpg/glazed/mixins/MixinClientPlayerInteractionManager.java new file mode 100644 index 00000000..9599791f --- /dev/null +++ b/src/main/java/com/nnpg/glazed/mixins/MixinClientPlayerInteractionManager.java @@ -0,0 +1,56 @@ +package com.nnpg.glazed.mixins; + +import com.nnpg.glazed.modules.main.LayerLock; +import meteordevelopment.meteorclient.systems.modules.Modules; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ClientPlayerInteractionManager.class) +public class MixinClientPlayerInteractionManager { + + @Inject(method = "interactBlock", at = @At("HEAD"), cancellable = true) + private void onInteractBlock(ClientPlayerEntity player, Hand hand, BlockHitResult hitResult, CallbackInfoReturnable cir) { + LayerLock module = Modules.get().get(LayerLock.class); + + if (module == null || !module.isActive() || !module.isLocked()) return; + + ItemStack stack = player.getStackInHand(hand); + + if (!(stack.getItem() instanceof BlockItem blockItem)) return; + + BlockPos placedPos = hitResult.getBlockPos().offset(hitResult.getSide()); + int placementY = placedPos.getY(); + + boolean blocked = false; + String reason = ""; + + // 1. Y-Level Check (Unterstützt jetzt Bereiche!) + if (placementY < module.getLockedYStart() || placementY > module.getLockedYEnd()) { + blocked = true; + reason = String.format("Wrong Y=%d (Range: %d-%d)", placementY, module.getLockedYStart(), module.getLockedYEnd()); + } + // 2. Block Filter Check (Nur wenn Advanced Settings an sind) + else if (!module.isBlockAllowed(blockItem)) { + blocked = true; + reason = "Block not allowed (" + blockItem.getName().getString() + ")"; + } + + if (blocked) { + if (module.showNotifications()) { + player.sendMessage(Text.literal("§c[LayerLock] Blocked §7— " + reason), true); + } + cir.setReturnValue(ActionResult.FAIL); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nnpg/glazed/modules/esp/ItemESP.java b/src/main/java/com/nnpg/glazed/modules/esp/ItemESP.java new file mode 100644 index 00000000..fc7435f1 --- /dev/null +++ b/src/main/java/com/nnpg/glazed/modules/esp/ItemESP.java @@ -0,0 +1,373 @@ +package com.nnpg.glazed.modules.esp; + +import com.nnpg.glazed.GlazedAddon; +import meteordevelopment.meteorclient.events.render.Render3DEvent; +import meteordevelopment.meteorclient.events.world.TickEvent; +import meteordevelopment.meteorclient.renderer.ShapeMode; +import meteordevelopment.meteorclient.settings.*; +import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.utils.player.ChatUtils; +import meteordevelopment.meteorclient.utils.player.PlayerUtils; +import meteordevelopment.meteorclient.utils.render.RenderUtils; +import meteordevelopment.meteorclient.utils.render.color.Color; +import meteordevelopment.meteorclient.utils.render.color.SettingColor; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.entity.Entity; +import net.minecraft.entity.ItemEntity; +import net.minecraft.item.*; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.tag.ItemTags; +import net.minecraft.text.Text; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.MathHelper; + +import java.util.*; + +public class ItemESP extends Module { + private final SettingGroup sgGeneral = this.settings.getDefaultGroup(); + private final SettingGroup sgAdvanced = settings.createGroup("Advanced Settings"); + + private final List defaultPlayerItems = new ArrayList<>(List.of( + // Diamond & Netherite Armor + Items.DIAMOND_HELMET, Items.DIAMOND_CHESTPLATE, Items.DIAMOND_LEGGINGS, Items.DIAMOND_BOOTS, + Items.NETHERITE_HELMET, Items.NETHERITE_CHESTPLATE, Items.NETHERITE_LEGGINGS, Items.NETHERITE_BOOTS, + // Diamond & Netherite Sword & Pickaxe + Items.DIAMOND_SWORD, Items.NETHERITE_SWORD, + Items.DIAMOND_PICKAXE, Items.NETHERITE_PICKAXE, + // Misc Items + Items.SHIELD, Items.ELYTRA, Items.MACE, Items.TRIDENT, + Items.NETHERITE_INGOT, Items.NETHERITE_BLOCK, + Items.DRAGON_HEAD, Items.PIGLIN_HEAD, + Items.ENCHANTED_GOLDEN_APPLE, Items.ANCIENT_DEBRIS, + // All Shulker Boxes + Items.SHULKER_BOX, Items.WHITE_SHULKER_BOX, Items.ORANGE_SHULKER_BOX, Items.MAGENTA_SHULKER_BOX, + Items.LIGHT_BLUE_SHULKER_BOX, Items.YELLOW_SHULKER_BOX, Items.LIME_SHULKER_BOX, Items.PINK_SHULKER_BOX, + Items.GRAY_SHULKER_BOX, Items.LIGHT_GRAY_SHULKER_BOX, Items.CYAN_SHULKER_BOX, Items.PURPLE_SHULKER_BOX, + Items.BLUE_SHULKER_BOX, Items.BROWN_SHULKER_BOX, Items.GREEN_SHULKER_BOX, Items.RED_SHULKER_BOX, + Items.BLACK_SHULKER_BOX + )); + + public ItemESP() { + super(GlazedAddon.esp, "item-Esp", "ESP Module that highlights only certain items."); + } + + // ── General Settings ─────────────────────────────────────────────────── + private final Setting> items = sgGeneral.add(new ItemListSetting.Builder() + .name("item-checker") + .description("Items to check for.") + .defaultValue(defaultPlayerItems) + .build() + ); + + public final Setting enchants = sgGeneral.add(new BoolSetting.Builder() + .name("enforce-item-enchants") + .description("Requires that armor and tools must be enchanted for module to detect.") + .defaultValue(true) + .build() + ); + + public final Setting certainenchants = sgGeneral.add(new BoolSetting.Builder() + .name("find-certain-item-enchants") + .description("Requires that armor and tools must be enchanted with these enchants.") + .defaultValue(false) + .visible(() -> enchants.get()) + .build() + ); + + private final Setting>> toolenchants = sgGeneral.add(new EnchantmentListSetting.Builder() + .name("Mining Tool Enchants") + .description("List of enchantments required.") + .visible(() -> enchants.get() && certainenchants.get()) + .defaultValue(Enchantments.EFFICIENCY, Enchantments.UNBREAKING, Enchantments.MENDING) + .build()); + + private final Setting>> swordenchants = sgGeneral.add(new EnchantmentListSetting.Builder() + .name("Sword Enchants") + .description("List of enchantments required.") + .visible(() -> enchants.get() && certainenchants.get()) + .defaultValue(Enchantments.UNBREAKING, Enchantments.MENDING) + .build()); + + private final Setting>> armorenchants = sgGeneral.add(new EnchantmentListSetting.Builder() + .name("Armor Enchants") + .description("List of enchantments required.") + .visible(() -> enchants.get() && certainenchants.get()) + .defaultValue(Enchantments.UNBREAKING, Enchantments.MENDING) + .build()); + + private final Setting>> maceenchants = sgGeneral.add(new EnchantmentListSetting.Builder() + .name("Mace Enchants") + .description("List of enchantments required.") + .visible(() -> enchants.get() && certainenchants.get()) + .defaultValue(Enchantments.UNBREAKING, Enchantments.MENDING) + .build()); + + private final Setting>> tridentenchants = sgGeneral.add(new EnchantmentListSetting.Builder() + .name("Trident Enchants") + .description("List of enchantments required.") + .visible(() -> enchants.get() && certainenchants.get()) + .defaultValue(Enchantments.UNBREAKING, Enchantments.MENDING) + .build()); + + private final Setting tracers = sgGeneral.add(new BoolSetting.Builder() + .name("Tracers") + .description("Add tracers to item detected") + .defaultValue(true) + .build() + ); + + // ── Advanced Settings (Reihenfolge ist wichtig für .visible() Referenzen!) ── + + public final Setting shapeMode = sgAdvanced.add(new EnumSetting.Builder() + .name("shape-mode") + .description("How the shapes are rendered.") + .defaultValue(ShapeMode.Both) + .build() + ); + + public final Setting fillOpacity = sgAdvanced.add(new DoubleSetting.Builder() + .name("fill-opacity") + .description("The opacity of the shape fill.") + .defaultValue(0.3) + .range(0, 1) + .sliderMax(1) + .build() + ); + + private final Setting fadeDistance = sgAdvanced.add(new DoubleSetting.Builder() + .name("fade-distance") + .description("The distance from an entity where the color begins to fade.") + .defaultValue(3) + .min(0) + .sliderMax(12) + .build() + ); + + private final Setting chatFeedback = sgAdvanced.add(new BoolSetting.Builder() + .name("chat-feedback") + .description("Display info about items in chat") + .defaultValue(false) // <-- Default OFF + .build() + ); + + private final Setting coordsInChat = sgAdvanced.add(new BoolSetting.Builder() + .name("display-coords-in-chat") + .description("Display coords of a detected item") + .visible(chatFeedback::get) + .defaultValue(false) // <-- Default OFF + .build() + ); + + private final Setting itemsColor = sgAdvanced.add(new ColorSetting.Builder() + .name("items-color") + .description("The item's bounding box and tracer color.") + .defaultValue(new SettingColor(255, 25, 255, 255)) + .build() + ); + + public final Setting distance = sgAdvanced.add(new BoolSetting.Builder() + .name("distance-colors") + .description("Changes the color of tracers depending on distance.") + .defaultValue(true) + .build() + ); + + private final Setting distantColor = sgAdvanced.add(new ColorSetting.Builder() + .name("distant-color") + .description("The item's bounding box and tracer color when you are far away.") + .defaultValue(new SettingColor(25, 255, 255, 255)) + .visible(distance::get) + .build() + ); + + public final Setting distanceInt = sgAdvanced.add(new IntSetting.Builder() + .name("distance-colors-threshold") + .description("The max distance for colors to change.") + .defaultValue(128) + .min(1) + .sliderRange(1, 1024) + .visible(distance::get) + .build() + ); + + // ── Fields ───────────────────────────────────────────────────────────── + + private final Color lineColor = new Color(); + private final Color sideColor = new Color(); + private final Color baseColor = new Color(); + + private int count; + private final Set scannedEntities = Collections.synchronizedSet(new HashSet<>()); + + // ── Logic ────────────────────────────────────────────────────────────── + + @EventHandler + private void onRender3D(Render3DEvent event) { + count = 0; + for (Entity entity : mc.world.getEntities()) { + if (!(entity instanceof ItemEntity itemEntity)) continue; + if (shouldSkip(itemEntity)) continue; + if (!scannedEntities.contains(entity)) { + StringBuilder message = new StringBuilder(itemEntity.getStack().getItem().getName().getString() + " found "); + if (chatFeedback.get()) { + if (coordsInChat.get()) message.append(" at ").append(entity.getBlockX()).append(", ").append(entity.getBlockY()).append(", ").append(entity.getBlockZ()); + ChatUtils.sendMsg(Text.of(message.toString())); + } + } + scannedEntities.add(entity); + drawBoundingBox(event, entity); + if (tracers.get()) drawTracer(event, entity); + count++; + } + } + + @Override + public void onActivate() { + scannedEntities.clear(); + } + + @Override + public void onDeactivate() { + scannedEntities.clear(); + } + + private void drawBoundingBox(Render3DEvent event, Entity entity) { + Color color = getColor(entity); + if (color != null) { + lineColor.set(color); + sideColor.set(color).a((int) (sideColor.a * fillOpacity.get())); + } + + double x = MathHelper.lerp(event.tickDelta, entity.lastRenderX, entity.getX()) - entity.getX(); + double y = MathHelper.lerp(event.tickDelta, entity.lastRenderY, entity.getY()) - entity.getY(); + double z = MathHelper.lerp(event.tickDelta, entity.lastRenderZ, entity.getZ()) - entity.getZ(); + Box box = entity.getBoundingBox(); + event.renderer.box(x + box.minX, y + box.minY, z + box.minZ, x + box.maxX, y + box.maxY, z + box.maxZ, sideColor, lineColor, shapeMode.get(), 0); + } + + private void drawTracer(Render3DEvent event, Entity entity) { + if (mc.options.hudHidden) return; + + Color tracersBaseColor = itemsColor.get(); + if (distance.get()){ + tracersBaseColor = getOpposingColor(tracersBaseColor, entity); + } + + // FIX: Für 1.21.4 lastRenderX/Y/Z statt lastX/Y/Z verwenden + double x = entity.lastRenderX + (entity.getX() - entity.lastRenderX) * event.tickDelta; + double y = entity.lastRenderY + (entity.getY() - entity.lastRenderY) * event.tickDelta; + double z = entity.lastRenderZ + (entity.getZ() - entity.lastRenderZ) * event.tickDelta; + + double height = entity.getBoundingBox().maxY - entity.getBoundingBox().minY; + y += height / 2; + + event.renderer.line(RenderUtils.center.x, RenderUtils.center.y, RenderUtils.center.z, x, y, z, tracersBaseColor); + } + private Color getOpposingColor(Color c, Entity e) { + Color interpolatedColor; + Color oppositeColor = distantColor.get(); + + double dist = Math.sqrt(mc.player.squaredDistanceTo(e)); + double maxDistance = distanceInt.get(); + double percent = MathHelper.clamp(dist / maxDistance, 0, 1); + + int r = (int) (c.r + (oppositeColor.r - c.r) * percent); + int g = (int) (c.g + (oppositeColor.g - c.g) * percent); + int b = (int) (c.b + (oppositeColor.b - c.b) * percent); + int a = c.a; + + interpolatedColor = new Color(r, g, b, a); + return interpolatedColor; + } + + public static boolean isTool(ItemStack itemStack) { + return itemStack.isIn(ItemTags.AXES) || + itemStack.isIn(ItemTags.HOES) || + itemStack.isIn(ItemTags.PICKAXES) || + itemStack.isIn(ItemTags.SHOVELS) || + itemStack.getItem() instanceof ShearsItem || + itemStack.getItem() instanceof FlintAndSteelItem; + } + + public static boolean isArmor(ItemStack itemStack) { + return itemStack.isIn(ItemTags.HEAD_ARMOR) || + itemStack.isIn(ItemTags.CHEST_ARMOR) || + itemStack.isIn(ItemTags.LEG_ARMOR) || + itemStack.isIn(ItemTags.FOOT_ARMOR); + } + + public boolean shouldSkip(ItemEntity entity) { + boolean skip = false; + if (enchants.get()) { + if (!certainenchants.get() && (isTool(entity.getStack()) || isArmor(entity.getStack()) || entity.getStack().isIn(ItemTags.SWORDS) || entity.getStack().getItem() instanceof FishingRodItem || entity.getStack().getItem() instanceof FlintAndSteelItem || entity.getStack().getItem() instanceof MaceItem || entity.getStack().getItem() instanceof ShearsItem || entity.getStack().getItem() instanceof ShieldItem || entity.getStack().getItem() instanceof TridentItem) && entity.getStack().isEnchantable() && entity.getStack().getEnchantments().isEmpty()) skip = true; + else if (certainenchants.get()){ + if (isTool(entity.getStack())){ + skip = compareEnchants(entity, toolenchants); + } else if ( entity.getStack().isIn(ItemTags.SWORDS)){ + skip = compareEnchants(entity, swordenchants); + } else if (isArmor(entity.getStack())){ + skip = compareEnchants(entity, armorenchants); + } else if (entity.getStack().getItem() instanceof MaceItem){ + skip = compareEnchants(entity, maceenchants); + } else if (entity.getStack().getItem() instanceof TridentItem){ + skip = compareEnchants(entity, tridentenchants); + } + } + } + if (!items.get().contains(entity.getStack().getItem())) skip = true; + return skip; + } + + private boolean compareEnchants(ItemEntity entity, Setting>> enchantsetting) { + boolean skip = false; + Set> itemenchants = new HashSet<>(); + entity.getStack().getEnchantments().getEnchantments().forEach(enchantment -> { + itemenchants.add(enchantment.getKey().get()); + }); + for (RegistryKey enchantKey : enchantsetting.get()) { + if (!itemenchants.contains(enchantKey)) { + skip = true; + break; + } + } + return skip; + } + + public Color getColor(Entity entity) { + double alpha = getFadeAlpha(entity); + if (alpha == 0) return null; + Color color = itemsColor.get(); // Name geändert von monstersColor zu itemsColor + if (distance.get()){ + color = getOpposingColor(color, entity); + } + return baseColor.set(color.r, color.g, color.b, (int) (color.a * alpha)); + } + + private double getFadeAlpha(Entity entity) { + double dist = PlayerUtils.squaredDistanceToCamera(entity.getX() + entity.getWidth() / 2, entity.getY() + entity.getEyeHeight(entity.getPose()), entity.getZ() + entity.getWidth() / 2); + double fadeDist = Math.pow(fadeDistance.get(), 2); + double alpha = 1; + if (dist <= fadeDist * fadeDist) alpha = (float) (Math.sqrt(dist) / fadeDist); + if (alpha <= 0.075) alpha = 0; + return alpha; + } + + @Override + public String getInfoString() { + return Integer.toString(count); + } + + @EventHandler + private void onPreTick(TickEvent.Pre event) { + if (mc.world != null){ + Iterable entities = mc.world.getEntities(); + scannedEntities.removeIf(entity -> { + Set entitySet = new HashSet<>(); + entities.forEach(entity1 -> entitySet.add(entity1)); + return !entitySet.contains(entity); + }); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nnpg/glazed/modules/main/LayerLock.java b/src/main/java/com/nnpg/glazed/modules/main/LayerLock.java new file mode 100644 index 00000000..55a60700 --- /dev/null +++ b/src/main/java/com/nnpg/glazed/modules/main/LayerLock.java @@ -0,0 +1,246 @@ +package com.nnpg.glazed.modules.main; + +import com.nnpg.glazed.GlazedAddon; +import meteordevelopment.meteorclient.events.meteor.KeyEvent; +import meteordevelopment.meteorclient.events.world.TickEvent; +import meteordevelopment.meteorclient.settings.*; +import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.systems.modules.Modules; +import meteordevelopment.meteorclient.systems.modules.player.FastUse; +import meteordevelopment.meteorclient.utils.misc.Keybind; +import meteordevelopment.meteorclient.utils.player.ChatUtils; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.item.BlockItem; +import net.minecraft.item.Item; +import net.minecraft.item.Items; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; + +import java.util.List; + +public class LayerLock extends Module { + + // ── Settings Groups ─────────────────────────────────────────────────── + private final SettingGroup sgGeneral = settings.getDefaultGroup(); + private final SettingGroup sgAdvanced = settings.createGroup("Advanced Settings"); // Erstellt den Trenner im GUI + + // ── Advanced Variablen (Zuerst deklariert um Vorwärtsreferenzen zu vermeiden) ── + + private final Setting advancedSettings = sgAdvanced.add(new BoolSetting.Builder() + .name("advanced-settings") + .description("Unlock Y-Level ranges and block filtering.") + .defaultValue(false) + .build() + ); + + private final Setting yLevelRange = sgAdvanced.add(new BoolSetting.Builder() + .name("y-level-range") + .description("Use two keys to define a min and max Y level instead of a single line.") + .defaultValue(false) + .visible(() -> advancedSettings.get()) + .build() + ); + + private final Setting startKey = sgAdvanced.add(new KeybindSetting.Builder() + .name("start-y-key") + .description("Set the minimum Y level.") + .defaultValue(Keybind.none()) + .visible(() -> advancedSettings.get() && yLevelRange.get()) + .build() + ); + + private final Setting endKey = sgAdvanced.add(new KeybindSetting.Builder() + .name("end-y-key") + .description("Set the maximum Y level.") + .defaultValue(Keybind.none()) + .visible(() -> advancedSettings.get() && yLevelRange.get()) + .build() + ); + + public enum BlockFilterMode { WHITELIST, BLACKLIST } + + private final Setting blockFilterMode = sgAdvanced.add(new EnumSetting.Builder() + .name("block-filter-mode") + .description("Choose if the list acts as a whitelist or blacklist.") + .defaultValue(BlockFilterMode.WHITELIST) + .visible(() -> advancedSettings.get()) + .build() + ); + + private final Setting> whitelistBlocks = sgAdvanced.add(new ItemListSetting.Builder() + .name("whitelist-blocks") + .description("Only these blocks can be placed.") + .defaultValue(Items.STONE, Items.COBBLESTONE) + .filter(item -> item instanceof BlockItem) + .visible(() -> advancedSettings.get() && blockFilterMode.get() == BlockFilterMode.WHITELIST) + .build() + ); + + private final Setting> blacklistBlocks = sgAdvanced.add(new ItemListSetting.Builder() + .name("blacklist-blocks") + .description("These blocks are NOT allowed to be placed.") + .defaultValue(Items.TNT) + .filter(item -> item instanceof BlockItem) + .visible(() -> advancedSettings.get() && blockFilterMode.get() == BlockFilterMode.BLACKLIST) + .build() + ); + + // ── Normale Variablen (Jetzt sicher deklariert, da sie oben referenziert werden) ── + + private final Setting setKey = sgGeneral.add(new KeybindSetting.Builder() + .name("set-key") + .description("Press while looking at a block to lock placement to that Y level.") + .defaultValue(Keybind.none()) + .visible(() -> !(advancedSettings.get() && yLevelRange.get())) + .build() + ); + + private final Setting usePlacementY = sgGeneral.add(new BoolSetting.Builder() + .name("use-placement-y") + .description("ON = lock to where the new block would appear (Y + 1). OFF = raw block Y.") + .defaultValue(true) + .build() + ); + + private final Setting notifications = sgGeneral.add(new BoolSetting.Builder() + .name("notifications") + .description("Show messages when the locked Y changes or a placement is blocked.") + .defaultValue(true) + .build() + ); + + private final Setting enableFastUse = sgGeneral.add(new BoolSetting.Builder() + .name("enable-fast-use") + .description("Automatically turns on Meteor's FastUse module.") + .defaultValue(false) + .build() + ); + + + // ── State ───────────────────────────────────────────────────────────── + private int lockedYStart = Integer.MIN_VALUE; + private int lockedYEnd = Integer.MIN_VALUE; + + private boolean fastUseWasEnabledBeforeUs = false; + private boolean wasFastUseSettingOnLastTick = false; + + public LayerLock() { + super(GlazedAddon.CATEGORY, "layer-lock", "Restricts block placement to Y levels and specific blocks."); + } + + @Override + public void onActivate() { + wasFastUseSettingOnLastTick = false; + fastUseWasEnabledBeforeUs = false; + if (notifications.get()) info("§aLayerLock Active§r."); + } + + @Override + public void onDeactivate() { + if (enableFastUse.get() && !fastUseWasEnabledBeforeUs) { + toggleModule(FastUse.class, false); + } + if (notifications.get()) info("§cLayerLock Disabled§r."); + } + + private void toggleModule(Class moduleClass, boolean enable) { + Module module = Modules.get().get(moduleClass); + if (module != null) { + if (enable && !module.isActive()) module.toggle(); + else if (!enable && module.isActive()) module.toggle(); + } + } + + @EventHandler + private void onTick(TickEvent.Pre event) { + boolean settingCurrentlyOn = enableFastUse.get(); + if (settingCurrentlyOn && !wasFastUseSettingOnLastTick) { + Module fastUse = Modules.get().get(FastUse.class); + if (fastUse != null) fastUseWasEnabledBeforeUs = fastUse.isActive(); + } + if (settingCurrentlyOn) toggleModule(FastUse.class, true); + wasFastUseSettingOnLastTick = settingCurrentlyOn; + } + + // ── Getter für den Mixin ────────────────────────────────────────────── + + public boolean isLocked() { + return lockedYStart != Integer.MIN_VALUE; + } + + public int getLockedYStart() { + return lockedYStart; + } + + public int getLockedYEnd() { + return lockedYEnd == Integer.MIN_VALUE ? lockedYStart : lockedYEnd; + } + + public boolean isAdvancedEnabled() { + return advancedSettings.get(); + } + + public boolean isBlockAllowed(BlockItem item) { + if (!advancedSettings.get()) return true; + if (blockFilterMode.get() == BlockFilterMode.WHITELIST) { + return whitelistBlocks.get().contains(item); + } else { + return !blacklistBlocks.get().contains(item); + } + } + + public boolean showNotifications() { + return notifications.get(); + } + + // ── Key handler ─────────────────────────────────────────────────────── + + private int calculateY(BlockHitResult hit) { + BlockPos hoveredPos = hit.getBlockPos(); + if (usePlacementY.get()) { + int y = hoveredPos.getY() + hit.getSide().getOffsetY(); + if (hit.getSide().getOffsetY() == 0) y = hoveredPos.getY(); + return y; + } else { + return hoveredPos.getY(); + } + } + + @EventHandler + private void onKey(KeyEvent event) { + if (mc.player == null || mc.world == null || mc.currentScreen != null) return; + + boolean rangeActive = advancedSettings.get() && yLevelRange.get(); + + if (!rangeActive && setKey.get().isPressed()) { + if (mc.crosshairTarget == null || mc.crosshairTarget.getType() != HitResult.Type.BLOCK) { + lockedYStart = Integer.MIN_VALUE; + lockedYEnd = Integer.MIN_VALUE; + if (notifications.get()) ChatUtils.info("§c[LayerLock] Lock cleared§r."); + return; + } + int y = calculateY((BlockHitResult) mc.crosshairTarget); + lockedYStart = y; + lockedYEnd = y; + if (notifications.get()) ChatUtils.info(String.format("§a[LayerLock] Locked §rto §eY=%d§r.", y)); + return; + } + + if (rangeActive && startKey.get().isPressed()) { + if (mc.crosshairTarget != null && mc.crosshairTarget.getType() == HitResult.Type.BLOCK) { + lockedYStart = calculateY((BlockHitResult) mc.crosshairTarget); + if (notifications.get()) ChatUtils.info(String.format("§a[LayerLock] Start Y §rset to §eY=%d§r.", lockedYStart)); + } + return; + } + + if (rangeActive && endKey.get().isPressed()) { + if (mc.crosshairTarget != null && mc.crosshairTarget.getType() == HitResult.Type.BLOCK) { + lockedYEnd = calculateY((BlockHitResult) mc.crosshairTarget); + if (notifications.get()) ChatUtils.info(String.format("§a[LayerLock] End Y §rset to §eY=%d§r.", lockedYEnd)); + } + return; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/nnpg/glazed/modules/main/SpawnerProtect.java b/src/main/java/com/nnpg/glazed/modules/main/SpawnerProtect.java index 890a04af..c06c5dbf 100644 --- a/src/main/java/com/nnpg/glazed/modules/main/SpawnerProtect.java +++ b/src/main/java/com/nnpg/glazed/modules/main/SpawnerProtect.java @@ -6,27 +6,26 @@ import meteordevelopment.meteorclient.systems.modules.Module; import meteordevelopment.meteorclient.systems.modules.Modules; import meteordevelopment.meteorclient.utils.player.ChatUtils; +import meteordevelopment.meteorclient.utils.player.FindItemResult; +import meteordevelopment.meteorclient.utils.player.InvUtils; import meteordevelopment.orbit.EventHandler; import net.minecraft.block.Blocks; import net.minecraft.client.network.AbstractClientPlayerEntity; -import net.minecraft.client.option.KeyBinding; +import net.minecraft.enchantment.Enchantments; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; -import net.minecraft.util.math.Direction; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.screen.GenericContainerScreenHandler; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; +import net.minecraft.item.PickaxeItem; +import net.minecraft.screen.GenericContainerScreenHandler; import net.minecraft.screen.slot.SlotActionType; 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.World; import meteordevelopment.meteorclient.systems.modules.misc.AutoReconnect; -import meteordevelopment.meteorclient.utils.player.FindItemResult; -import meteordevelopment.meteorclient.utils.player.InvUtils; -import net.minecraft.enchantment.Enchantments; -import net.minecraft.item.PickaxeItem; import java.net.URI; import java.net.http.HttpClient; @@ -36,371 +35,285 @@ import java.util.*; public class SpawnerProtect extends Module { - private final SettingGroup sgGeneral = settings.getDefaultGroup(); private final SettingGroup sgWhitelist = settings.createGroup("Whitelist"); private final SettingGroup sgWebhook = settings.createGroup("Webhook"); + // ── Webhook Settings ─────────────────────────────────────────────────── private final Setting webhook = sgWebhook.add(new BoolSetting.Builder() - .name("webhook") - .description("Enable webhook notifications") - .defaultValue(false) - .build()); + .name("webhook").description("Enable webhook notifications").defaultValue(false).build()); private final Setting webhookUrl = sgWebhook.add(new StringSetting.Builder() - .name("webhook-url") - .description("Discord webhook URL for notifications") - .defaultValue("") - .visible(webhook::get) - .build()); + .name("webhook-url").description("Discord webhook URL").defaultValue("") + .visible(webhook::get).build()); private final Setting selfPing = sgWebhook.add(new BoolSetting.Builder() - .name("self-ping") - .description("Ping yourself in the webhook message") - .defaultValue(false) - .visible(webhook::get) - .build()); + .name("self-ping").description("Ping yourself in the webhook").defaultValue(false) + .visible(webhook::get).build()); private final Setting discordId = sgWebhook.add(new StringSetting.Builder() - .name("discord-id") - .description("Your Discord user ID for pinging") - .defaultValue("") - .visible(() -> webhook.get() && selfPing.get()) - .build()); + .name("discord-id").description("Your Discord user ID").defaultValue("") + .visible(() -> webhook.get() && selfPing.get()).build()); + // ── General Settings (Oben) ─────────────────────────────────────────── private final Setting notifications = sgGeneral.add(new BoolSetting.Builder() - .name("notifications") - .description("Show chat feedback.") - .defaultValue(true) - .build()); + .name("notifications").description("Show chat feedback.").defaultValue(true).build()); private final Setting spawnerRange = sgGeneral.add(new IntSetting.Builder() - .name("spawner-range") - .description("Range to check for remaining spawners") - .defaultValue(16) - .min(1) - .max(50) - .sliderMax(50) - .build()); + .name("spawner-range").description("Range to check for spawners").defaultValue(16) + .min(1).max(50).sliderMax(50).build()); private final Setting emergencyDistance = sgGeneral.add(new IntSetting.Builder() - .name("emergency-distance") - .description("Distance in blocks where player triggers immediate disconnect (0 to disable).") - .defaultValue(7) - .min(0) - .max(20) - .sliderMax(20) - .build()); + .name("emergency-distance").description("Instant disconnect distance (0 to disable)").defaultValue(7) + .min(0).max(20).sliderMax(20).build()); private final Setting minDetectionRange = sgGeneral.add(new IntSetting.Builder() - .name("min-detection-range") - .description("Minimum distance to detect a player (ignore players closer than this).") - .defaultValue(0) - .min(0) - .max(50) - .sliderMax(50) - .build()); + .name("min-detection-range").description("Minimum distance to detect a player").defaultValue(0) + .min(0).max(50).sliderMax(50).build()); private final Setting maxDetectionRange = sgGeneral.add(new IntSetting.Builder() - .name("max-detection-range") - .description("Maximum distance to detect a player.") - .defaultValue(50) - .min(1) - .max(100) - .sliderMax(100) - .build()); + .name("max-detection-range").description("Maximum distance to detect a player").defaultValue(50) + .min(1).max(100).sliderMax(100).build()); private final Setting spawnerCheckDelay = sgGeneral.add(new IntSetting.Builder() - .name("spawner-check-delay-ms") - .description("Delay in milliseconds before confirming all spawners are gone") - .defaultValue(3000) - .min(1000) - .max(10000) - .sliderMax(10000) - .build()); + .name("spawner-check-delay-ms").description("Delay before confirming spawners are gone").defaultValue(3000) + .min(1000).max(10000).sliderMax(10000).build()); private final Setting spawnerTimeout = sgGeneral.add(new IntSetting.Builder() - .name("spawner-timeout-ms") - .description("Time in milliseconds before skipping a spawner that can't be mined") - .defaultValue(4000) - .min(4000) - .max(30000) - .sliderMax(30000) - .build()); + .name("spawner-timeout-ms").description("Time to wait before rescanning a spawner (important for stacked spawners)").defaultValue(3000) + .min(1000).max(30000).sliderMax(30000).build()); private final Setting depositToEChest = sgGeneral.add(new BoolSetting.Builder() - .name("deposit-to-echest") - .description("Deposit spawners into ender chest after mining.") + .name("deposit-to-echest").description("Deposit spawners into ender chest").defaultValue(true).build()); + + private final Setting disableAutoReconnect = sgGeneral.add(new BoolSetting.Builder() + .name("disable-auto-reconnect") + .description("Disables Meteor AutoReconnect when this module disconnects you.") .defaultValue(true) .build()); - private final Setting enableWhitelist = sgWhitelist.add(new BoolSetting.Builder() - .name("enable-whitelist") - .description("Enable player whitelist (whitelisted players won't trigger protection)") - .defaultValue(false) + private final Setting detectServerRestarts = sgGeneral.add(new BoolSetting.Builder() + .name("detect-server-restarts") + .description("Ignores players if you are near spawn (within 1000 blocks of 0,0,0) to prevent false triggers on restarts.") + .defaultValue(true) .build()); + // ── General Settings (Ganz unten in der Liste) ──────────────────────── + private final Setting debug = sgGeneral.add(new BoolSetting.Builder() + .name("debug").description("Spam debug messages in chat").defaultValue(false).build()); + + // ── Whitelist Settings ──────────────────────────────────────────────── + private final Setting enableWhitelist = sgWhitelist.add(new BoolSetting.Builder() + .name("enable-whitelist").description("Ignore whitelisted players").defaultValue(false).build()); + private final Setting> whitelistPlayers = sgWhitelist.add(new StringListSetting.Builder() - .name("whitelisted-players") - .description("List of player names to ignore") - .defaultValue(new ArrayList<>()) - .visible(enableWhitelist::get) - .build()); + .name("whitelisted-players").description("Player names to ignore").defaultValue(new ArrayList<>()) + .visible(enableWhitelist::get).build()); private final Setting> depositBlacklist = sgGeneral.add(new ItemListSetting.Builder() - .name("deposit-blacklist") - .description("Items that will never be deposited into the ender chest.") - .defaultValue(Arrays.asList( - Items.ENDER_PEARL, - Items.END_CRYSTAL, - Items.OBSIDIAN, - Items.RESPAWN_ANCHOR, - Items.GLOWSTONE, - Items.TOTEM_OF_UNDYING)) - .visible(depositToEChest::get) - .build()); - - private enum State { - IDLE, - GOING_TO_SPAWNERS, - GOING_TO_CHEST, - OPENING_CHEST, - DEPOSITING_ITEMS, - DISCONNECTING, - WORLD_CHANGED_ONCE, - WORLD_CHANGED_TWICE - } + .name("deposit-blacklist").description("Items to keep when depositing") + .defaultValue(Arrays.asList(Items.ENDER_PEARL, Items.END_CRYSTAL, Items.OBSIDIAN, Items.RESPAWN_ANCHOR, Items.GLOWSTONE, Items.TOTEM_OF_UNDYING)) + .visible(depositToEChest::get).build()); + // ── State Machine ───────────────────────────────────────────────────── + private enum State { IDLE, GOING_TO_SPAWNERS, GOING_TO_CHEST, OPENING_CHEST, DEPOSITING_ITEMS, DISCONNECTING, WORLD_CHANGED_ONCE, WORLD_CHANGED_TWICE } private State currentState = State.IDLE; + + // ── Runtime Variables ───────────────────────────────────────────────── + private int stateTick = 0; + private int transferDelayCounter = 0; + private int ticksSinceLastClick = 0; + private int currentPickaxeSlot = -1; + private String detectedPlayer = ""; private long detectionTime = 0; private boolean spawnersMinedSuccessfully = false; private boolean itemsDepositedSuccessfully = false; - private int tickCounter = 0; - private int transferDelayCounter = 0; - private int lastProcessedSlot = -1; + private boolean emergencyDisconnect = false; + private String emergencyReason = ""; - private boolean sneaking = false; private BlockPos currentTarget = null; private long noSpawnerStartTime = -1; private long currentTargetStartTime = -1; private BlockPos targetChest = null; - private int chestOpenAttempts = 0; - private boolean emergencyDisconnect = false; - private String emergencyReason = ""; private World trackedWorld = null; private int worldChangeCount = 0; - // If there are this many or more other players online, do not activate - // protection private final int PLAYER_COUNT_THRESHOLD = 3; + private float targetYaw, targetPitch; private boolean rotating = false; - private final float ROTATION_SPEED = 8.0f; - private long respawnWaitStart = -1; - private final Set invalidSpawners = new HashSet<>(); + private static final float ROTATION_SPEED = 8.0f; + public SpawnerProtect() { - super(GlazedAddon.CATEGORY, "spawner-protect", - "Breaks spawners and puts them in your inv when a player is detected"); + super(GlazedAddon.CATEGORY, "spawner-protect", "Breaks spawners and secures them when a player is detected"); } @Override public void onActivate() { resetState(); - configureLegitMining(); - if (mc.world != null) { trackedWorld = mc.world; worldChangeCount = 0; - if (notifications.get()) - info("SpawnerProtect activated - Monitoring world: " + mc.world.getRegistryKey().getValue()); - if (notifications.get()) - info("Monitoring for players..."); + info("Monitoring world: " + mc.world.getRegistryKey().getValue() + " for players..."); } + ChatUtils.warning("Ensure you have an empty inventory, silk touch pickaxe, and placed ender chest!"); + } - if (notifications.get()) - ChatUtils.warning( - "Make sure to have an empty inventory with only a silk touch pickaxe and an ender chest nearby!"); + @Override + public void onDeactivate() { + cleanupActions(); } private void resetState() { + cleanupActions(); currentState = State.IDLE; + stateTick = 0; + transferDelayCounter = 0; + ticksSinceLastClick = 0; + currentPickaxeSlot = -1; detectedPlayer = ""; detectionTime = 0; spawnersMinedSuccessfully = false; itemsDepositedSuccessfully = false; - tickCounter = 0; - transferDelayCounter = 0; - lastProcessedSlot = -1; - sneaking = false; - currentTarget = null; - noSpawnerStartTime = -1; - currentTargetStartTime = -1; - targetChest = null; - chestOpenAttempts = 0; emergencyDisconnect = false; emergencyReason = ""; - invalidSpawners.clear(); + currentTarget = null; + targetChest = null; rotating = false; + noSpawnerStartTime = -1; + currentTargetStartTime = -1; } - private void configureLegitMining() { - if (notifications.get()) - info("Manual mining mode activated"); - } - - private void disableAutoReconnectIfEnabled() { - Module autoReconnect = Modules.get().get(AutoReconnect.class); - if (autoReconnect != null && autoReconnect.isActive()) { - autoReconnect.toggle(); - if (notifications.get()) - info("AutoReconnect disabled due to player detection"); + private void cleanupActions() { + if (mc.player != null) { + mc.options.sneakKey.setPressed(false); + mc.options.attackKey.setPressed(false); + mc.options.forwardKey.setPressed(false); + mc.options.jumpKey.setPressed(false); + mc.player.closeHandledScreen(); } } - @EventHandler - private void onTick(TickEvent.Pre event) { - if (mc.player == null || mc.world == null) - return; - if (rotating) { - float yaw = mc.player.getYaw(); - float pitch = mc.player.getPitch(); - - float yawDiff = wrapDegrees(targetYaw - yaw); - float pitchDiff = targetPitch - pitch; - - float newYaw = yaw + Math.signum(yawDiff) * Math.min(Math.abs(yawDiff), ROTATION_SPEED); - float newPitch = pitch + Math.signum(pitchDiff) * Math.min(Math.abs(pitchDiff), ROTATION_SPEED); - - mc.player.setYaw(newYaw); - mc.player.setPitch(newPitch); - - if (Math.abs(yawDiff) < 1f && Math.abs(pitchDiff) < 1f) { - rotating = false; + private void changeState(State newState) { + if (currentState != newState) { + if (debug.get()) info("DEBUG: State changed from " + currentState + " to " + newState); + currentState = newState; + stateTick = 0; + if (newState != State.DISCONNECTING) { + mc.options.attackKey.setPressed(false); + mc.options.forwardKey.setPressed(false); } } + } - if (currentState == State.GOING_TO_SPAWNERS) { - mc.options.sneakKey.setPressed(true); + // ── Sneak Steuerung ─────────────────────────────────────────────────── + private void updateSneakState() { + if (mc.player == null) return; + boolean shouldSneak = (currentState == State.GOING_TO_SPAWNERS || currentState == State.GOING_TO_CHEST); + + if (shouldSneak) { + if (!mc.options.sneakKey.isPressed()) mc.options.sneakKey.setPressed(true); } else { - mc.options.sneakKey.setPressed(mc.options.sneakKey.isPressed()); + if (mc.options.sneakKey.isPressed()) mc.options.sneakKey.setPressed(false); } + } + + // ── Haupt Loop ──────────────────────────────────────────────────────── + @EventHandler + private void onTick(TickEvent.Pre event) { + if (mc.player == null || mc.world == null) return; - tickCounter++; + updateSneakState(); + handleRotation(); + stateTick++; if (mc.world != trackedWorld) { handleWorldChange(); return; } - if (currentState == State.WORLD_CHANGED_ONCE) { - return; - } - - if (currentState == State.WORLD_CHANGED_TWICE) { - currentState = State.IDLE; - if (notifications.get()) - info("Returned to spawner world - resuming player monitoring"); - } + if (currentState == State.WORLD_CHANGED_ONCE || currentState == State.WORLD_CHANGED_TWICE) return; - if (checkEmergencyDisconnect()) { + // FIX: Wenn wir uns bereits im Disconnecting-State befinden, + // überspringe Player-Checks etc. und führe SOFORT den Disconnect aus. + if (currentState == State.DISCONNECTING) { + handleDisconnecting(); return; } + if (checkEmergencyDisconnect()) return; + if (transferDelayCounter > 0) { transferDelayCounter--; return; } switch (currentState) { - case IDLE: - checkForPlayers(); - break; - case GOING_TO_SPAWNERS: - handleGoingToSpawners(); - break; - case GOING_TO_CHEST: - handleGoingToChest(); - break; - case OPENING_CHEST: - handleOpeningChest(); - break; - case DEPOSITING_ITEMS: - handleDepositingItems(); - break; - case DISCONNECTING: - handleDisconnecting(); - break; - case WORLD_CHANGED_ONCE: - case WORLD_CHANGED_TWICE: - break; + case IDLE -> checkForPlayers(); + case GOING_TO_SPAWNERS -> handleGoingToSpawners(); + case GOING_TO_CHEST -> handleGoingToChest(); + case OPENING_CHEST -> handleOpeningChest(); + case DEPOSITING_ITEMS -> handleDepositingItems(); + // DISCONNECTING wurde hier rausgenommen, da es jetzt oben abgefangen wird + default -> {} } } - private void handleWorldChange() { - worldChangeCount++; - trackedWorld = mc.world; + private void handleRotation() { + if (!rotating) return; + float yawDiff = wrapDegrees(targetYaw - mc.player.getYaw()); + float pitchDiff = targetPitch - mc.player.getPitch(); - if (worldChangeCount == 1) { - currentState = State.WORLD_CHANGED_ONCE; - if (notifications.get()) - info("World changed (TP to spawn) - pausing player detection until return"); - } else if (worldChangeCount == 2) { - currentState = State.WORLD_CHANGED_TWICE; - worldChangeCount = 0; - if (notifications.get()) - info("World changed (back to spawners) - will resume monitoring"); - } + mc.player.setYaw(mc.player.getYaw() + Math.signum(yawDiff) * Math.min(Math.abs(yawDiff), ROTATION_SPEED)); + mc.player.setPitch(mc.player.getPitch() + Math.signum(pitchDiff) * Math.min(Math.abs(pitchDiff), ROTATION_SPEED)); + + if (Math.abs(yawDiff) < 1f && Math.abs(pitchDiff) < 1f) rotating = false; } private float wrapDegrees(float value) { value %= 360.0f; - if (value >= 180.0f) - value -= 360.0f; - if (value < -180.0f) - value += 360.0f; + if (value >= 180.0f) value -= 360.0f; + if (value < -180.0f) value += 360.0f; return value; } + private void handleWorldChange() { + worldChangeCount++; + trackedWorld = mc.world; + if (worldChangeCount == 1) { + changeState(State.WORLD_CHANGED_ONCE); + if (notifications.get()) info("World changed - pausing detection..."); + } else if (worldChangeCount >= 2) { + changeState(State.WORLD_CHANGED_TWICE); + worldChangeCount = 0; + if (notifications.get()) info("Returned to spawner world - resuming..."); + changeState(State.IDLE); + } + } + + // ── Player Detection ────────────────────────────────────────────────── private boolean checkEmergencyDisconnect() { - if (emergencyDistance.get() <= 0) - return false; + if (emergencyDistance.get() <= 0) return false; + + // Server Restart Schutz + if (detectServerRestarts.get() && isNearSpawn()) return false; long otherPlayers = mc.world.getPlayers().stream().filter(p -> p != mc.player).count(); - if (otherPlayers >= PLAYER_COUNT_THRESHOLD) - return false; + if (otherPlayers >= PLAYER_COUNT_THRESHOLD) return false; for (PlayerEntity player : mc.world.getPlayers()) { - if (player == mc.player || player == null || !(player instanceof AbstractClientPlayerEntity)) - continue; - - String playerName = player.getGameProfile().getName(); - - if (isPlayerWhitelisted(playerName)) { - continue; - } - - double distance = mc.player.distanceTo(player); - if (distance <= emergencyDistance.get()) { - if (notifications.get()) - info("EMERGENCY: Player " + playerName + " came too close (" + String.format("%.1f", distance) - + " blocks)!"); + if (!(player instanceof AbstractClientPlayerEntity) || player == mc.player) continue; + if (isPlayerWhitelisted(player.getGameProfile().getName())) continue; + if (mc.player.distanceTo(player) <= emergencyDistance.get()) { emergencyDisconnect = true; - emergencyReason = "User " + playerName + " came too close"; - - toggle(); - if (mc.world != null) { - mc.world.disconnect(); - } - - detectedPlayer = playerName; + emergencyReason = player.getGameProfile().getName() + " came too close"; + detectedPlayer = player.getGameProfile().getName(); detectionTime = System.currentTimeMillis(); - disableAutoReconnectIfEnabled(); - - currentState = State.DISCONNECTING; + changeState(State.DISCONNECTING); return true; } } @@ -408,604 +321,312 @@ private boolean checkEmergencyDisconnect() { } private void checkForPlayers() { - // nigga + // Server Restart Schutz + if (detectServerRestarts.get() && isNearSpawn()) return; + long otherPlayers = mc.world.getPlayers().stream().filter(p -> p != mc.player).count(); - if (otherPlayers >= PLAYER_COUNT_THRESHOLD) - return; + if (otherPlayers >= PLAYER_COUNT_THRESHOLD) return; for (PlayerEntity player : mc.world.getPlayers()) { - if (player == mc.player || player == null || !(player instanceof AbstractClientPlayerEntity)) - continue; - + if (!(player instanceof AbstractClientPlayerEntity) || player == mc.player) continue; double distance = mc.player.getPos().distanceTo(player.getPos()); - if (distance < minDetectionRange.get() || distance > maxDetectionRange.get()) - continue; + if (distance < minDetectionRange.get() || distance > maxDetectionRange.get()) continue; - String playerName = player.getGameProfile().getName(); + String name = player.getGameProfile().getName(); + if (isPlayerWhitelisted(name)) continue; - if (isPlayerWhitelisted(playerName)) { - continue; - } - - detectedPlayer = playerName; + detectedPlayer = name; detectionTime = System.currentTimeMillis(); - - if (notifications.get()) - info("SpawnerProtect: Player detected at " + String.format("%.1f", distance) + " blocks - " - + detectedPlayer); - disableAutoReconnectIfEnabled(); - - currentState = State.GOING_TO_SPAWNERS; - if (notifications.get()) - info("Player detected! Starting protection sequence..."); - + if (notifications.get()) info("Player detected: " + name + " (" + String.format("%.1f", distance) + " blocks)"); + changeState(State.GOING_TO_SPAWNERS); break; } } - private boolean isPlayerWhitelisted(String playerName) { - // Check global admin list first - AdminList adminList = Modules.get().get(AdminList.class); - if (adminList != null && adminList.isActive() && adminList.isAdmin(playerName)) { - return true; - } - - if (!enableWhitelist.get() || whitelistPlayers.get().isEmpty()) { - return false; - } - - return whitelistPlayers.get().stream() - .anyMatch(whitelistedName -> whitelistedName.equalsIgnoreCase(playerName)); + private boolean isNearSpawn() { + // Berechnet nur den horizontalen Abstand (X und Z), ignoriert Y (Höhe) + double distXZ = Math.sqrt(mc.player.getX() * mc.player.getX() + mc.player.getZ() * mc.player.getZ()); + return distXZ <= 1000; } - private FindItemResult findSilkTouchPickaxe() { - return InvUtils.find(stack -> { - if (!(stack.getItem() instanceof PickaxeItem)) - return false; - - var enchantments = stack.getEnchantments(); - for (var entry : enchantments.getEnchantmentEntries()) { - if (entry.getKey().matchesKey(Enchantments.SILK_TOUCH)) - return true; - } - return false; - }); + private boolean isPlayerWhitelisted(String name) { + AdminList adminList = Modules.get().get(AdminList.class); + if (adminList != null && adminList.isActive() && adminList.isAdmin(name)) return true; + if (!enableWhitelist.get()) return false; + return whitelistPlayers.get().stream().anyMatch(w -> w.equalsIgnoreCase(name)); } + // ── Spawner Mining Logic ────────────────────────────────────────────── private void handleGoingToSpawners() { - mc.options.sneakKey.setPressed(true); - - // Check if current target is still valid before doing anything else - if (currentTarget != null && mc.world.getBlockState(currentTarget).getBlock() != Blocks.SPAWNER) { - currentTarget = null; - currentTargetStartTime = -1; - stopBreaking(); - noSpawnerStartTime = -1; - return; - } - if (currentTarget == null) { currentTarget = findNearestSpawner(); - if (currentTarget == null) { - // Start the delay timer when no spawners are found if (noSpawnerStartTime == -1) { noSpawnerStartTime = System.currentTimeMillis(); - if (notifications.get()) - info("No spawners found, waiting " + spawnerCheckDelay.get() + "ms to confirm..."); - return; + if (debug.get()) info("DEBUG: No spawners found, starting delay timer..."); } - - // Check if the delay has passed - long elapsed = System.currentTimeMillis() - noSpawnerStartTime; - if (elapsed < spawnerCheckDelay.get()) { - // Still waiting, keep checking for new spawners - return; + if (System.currentTimeMillis() - noSpawnerStartTime >= spawnerCheckDelay.get()) { + if (debug.get()) info("DEBUG: Delay passed. No spawners left. Moving to chest."); + changeState(State.GOING_TO_CHEST); } - - // Delay passed, confirm no spawners and move to chest - invalidSpawners.clear(); - stopBreaking(); - currentState = State.GOING_TO_CHEST; - noSpawnerStartTime = -1; - if (notifications.get()) - info("No more spawners in range after delay, moving to ender chest..."); return; } - - // Found a new spawner, start the timer + noSpawnerStartTime = -1; currentTargetStartTime = System.currentTimeMillis(); - if (notifications.get()) - info("Found spawner at " + currentTarget + ", distance: " + - String.format("%.1f", Math.sqrt(currentTarget.getSquaredDistance(mc.player.getPos())))); + if (debug.get()) info("DEBUG: Found spawner at " + currentTarget.toShortString()); } - - // Reset the no-spawner timer when we find a spawner - noSpawnerStartTime = -1; - // Check if we've been trying to mine this spawner for too long if (currentTargetStartTime != -1) { long timeTrying = System.currentTimeMillis() - currentTargetStartTime; if (timeTrying > spawnerTimeout.get()) { - if (notifications.get()) - info("Timeout mining spawner at " + currentTarget + " after " + spawnerTimeout.get() + "ms, skipping..."); - invalidSpawners.add(currentTarget); + if (debug.get()) info("DEBUG: Timeout reached. Rescanning area..."); currentTarget = null; currentTargetStartTime = -1; - stopBreaking(); + currentPickaxeSlot = -1; + mc.options.attackKey.setPressed(false); return; } } Direction side = getExposedFaceSide(currentTarget); - - // Start rotating towards the spawner lookAtBlock(currentTarget, side); - // Mine if we're looking at the spawner - if (mc.crosshairTarget instanceof BlockHitResult hit - && hit.getBlockPos().equals(currentTarget)) { - + if (mc.crosshairTarget instanceof BlockHitResult hit && hit.getBlockPos().equals(currentTarget)) { FindItemResult pickaxe = findSilkTouchPickaxe(); if (!pickaxe.found()) { - stopBreaking(); - currentState = State.GOING_TO_CHEST; - if (notifications.get()) - info("No silk touch pickaxe found, moving to ender chest..."); + if (debug.get()) error("DEBUG: No Silk Touch Pickaxe found!"); + changeState(State.GOING_TO_CHEST); return; } - InvUtils.swap(pickaxe.slot(), true); + if (mc.player.getInventory().selectedSlot != pickaxe.slot()) { + InvUtils.swap(pickaxe.slot(), true); + currentPickaxeSlot = pickaxe.slot(); + if (debug.get()) info("DEBUG: Swapped to pickaxe in slot " + pickaxe.slot()); + } mc.options.attackKey.setPressed(true); mc.interactionManager.updateBlockBreakingProgress(currentTarget, hit.getSide()); + } else { + mc.options.attackKey.setPressed(false); } - // Don't immediately mark as invalid - let the timeout handle it } private BlockPos findNearestSpawner() { BlockPos playerPos = mc.player.getBlockPos(); BlockPos nearest = null; double nearestDistance = Double.MAX_VALUE; - - // Use spawnerRange setting for distance check (squared for efficiency) - double maxDistanceSq = spawnerRange.get() * spawnerRange.get(); - - for (BlockPos pos : BlockPos.iterate( - playerPos.add(-spawnerRange.get(), -spawnerRange.get(), -spawnerRange.get()), - playerPos.add(spawnerRange.get(), spawnerRange.get(), spawnerRange.get()))) { + double maxDistSq = spawnerRange.get() * spawnerRange.get(); + for (BlockPos pos : BlockPos.iterate(playerPos.add(-spawnerRange.get(), -spawnerRange.get(), -spawnerRange.get()), playerPos.add(spawnerRange.get(), spawnerRange.get(), spawnerRange.get()))) { if (mc.world.getBlockState(pos).getBlock() != Blocks.SPAWNER) continue; - if (invalidSpawners.contains(pos)) continue; - - double distanceSq = pos.getSquaredDistance(mc.player.getPos()); - if (distanceSq > maxDistanceSq) continue; - - if (distanceSq < nearestDistance) { - nearestDistance = distanceSq; + double distSq = pos.getSquaredDistance(mc.player.getPos()); + if (distSq < nearestDistance && distSq <= maxDistSq) { + nearestDistance = distSq; nearest = pos.toImmutable(); } } - return nearest; } - private boolean hasLineOfSight(BlockPos pos, Direction side) { - Vec3d eyePos = mc.player.getEyePos(); - Vec3d targetPos = Vec3d.ofCenter(pos).add(Vec3d.of(side.getVector()).multiply(0.5)); - // Use COLLIDER shape type which is more lenient for mining checks - BlockHitResult result = mc.world.raycast(new net.minecraft.world.RaycastContext( - eyePos, - targetPos, - net.minecraft.world.RaycastContext.ShapeType.COLLIDER, - net.minecraft.world.RaycastContext.FluidHandling.NONE, - mc.player - )); - - if (result == null) return true; // No hit means clear path - - // If we hit the spawner itself, we have line of sight - if (result.getType() == net.minecraft.util.hit.HitResult.Type.BLOCK - && result.getBlockPos().equals(pos)) { - return true; - } - - // If we hit a block that is air or non-solid, allow it - if (result.getType() == net.minecraft.util.hit.HitResult.Type.BLOCK) { - BlockPos hitPos = result.getBlockPos(); - // Allow if we hit air or pass-through blocks - if (mc.world.getBlockState(hitPos).isAir()) { - return true; - } - // Allow if we hit a non-full block (like tall grass, water, etc.) - if (!mc.world.getBlockState(hitPos).isFullCube(mc.world, hitPos)) { - return true; - } - // Special case: if the spawner is directly below us, allow mining - if (hitPos.equals(mc.player.getBlockPos()) || hitPos.equals(mc.player.getBlockPos().down())) { - return true; + private FindItemResult findSilkTouchPickaxe() { + return InvUtils.find(stack -> { + if (!(stack.getItem() instanceof PickaxeItem)) return false; + for (var entry : stack.getEnchantments().getEnchantmentEntries()) { + if (entry.getKey().matchesKey(Enchantments.SILK_TOUCH)) return true; } - } - - return false; - } - private void lookAtBlock(BlockPos pos) { - lookAtBlock(pos, Direction.UP); - } - - private void lookAtBlock(BlockPos pos, Direction side) { - Vec3d targetPos = Vec3d.ofCenter(pos).add(Vec3d.of(side.getVector()).multiply(0.5)); - Vec3d playerPos = mc.player.getEyePos(); - Vec3d dir = targetPos.subtract(playerPos).normalize(); - - targetYaw = (float) Math.toDegrees(Math.atan2(-dir.x, dir.z)); - targetPitch = (float) Math.toDegrees(-Math.asin(dir.y)); - rotating = true; + return false; + }); } private Direction getExposedFaceSide(BlockPos pos) { - // Check if player is standing directly on this spawner - BlockPos playerBlockPos = mc.player.getBlockPos(); - if (pos.equals(playerBlockPos.down())) { - // Player is standing on the spawner, try to find any exposed side - // or return DOWN if we need to mine from below (unlikely but possible) - } - for (Direction side : Direction.values()) { BlockPos neighbor = pos.offset(side); - if (mc.world.getBlockState(neighbor).isAir() - || !mc.world.getBlockState(neighbor).isFullCube(mc.world, neighbor)) { + if (mc.world.getBlockState(neighbor).isAir() || !mc.world.getBlockState(neighbor).isFullCube(mc.world, neighbor)) { return side; } } - return Direction.UP; } - private void stopBreaking() { - mc.options.attackKey.setPressed(false); + private void lookAtBlock(BlockPos pos, Direction side) { + Vec3d targetPos = Vec3d.ofCenter(pos).add(Vec3d.of(side.getVector()).multiply(0.5)); + Vec3d dir = targetPos.subtract(mc.player.getEyePos()).normalize(); + targetYaw = (float) Math.toDegrees(Math.atan2(-dir.x, dir.z)); + targetPitch = (float) Math.toDegrees(-Math.asin(dir.y)); + rotating = true; } + // ── Ender Chest Logic ───────────────────────────────────────────────── private void handleGoingToChest() { - if (!depositToEChest.get()) { - currentState = State.DISCONNECTING; - if (notifications.get()) - info("Deposit to ender chest disabled, disconnecting..."); - return; - } + if (!depositToEChest.get()) { changeState(State.DISCONNECTING); return; } if (targetChest == null) { targetChest = findNearestEnderChest(); if (targetChest == null) { - if (notifications.get()) - info("No ender chest found nearby!"); - currentState = State.DISCONNECTING; + if (debug.get()) error("DEBUG: No ender chest found!"); + changeState(State.DISCONNECTING); return; } - if (notifications.get()) - info("Found ender chest at " + targetChest); + if (debug.get()) info("DEBUG: Found ender chest at " + targetChest.toShortString()); } moveTowardsBlock(targetChest); if (mc.player.getBlockPos().getSquaredDistance(targetChest) <= 9) { - currentState = State.OPENING_CHEST; - chestOpenAttempts = 0; - if (notifications.get()) - info("Reached ender chest. Attempting to open..."); - } - - if (tickCounter > 600) { - if (notifications.get()) - ChatUtils.error("Timed out trying to reach ender chest!"); - currentState = State.DISCONNECTING; + if (debug.get()) info("DEBUG: Reached ender chest distance."); + changeState(State.OPENING_CHEST); + } else if (stateTick > 600) { + if (debug.get()) error("DEBUG: Timed out moving to chest!"); + changeState(State.DISCONNECTING); } } private BlockPos findNearestEnderChest() { - BlockPos playerPos = mc.player.getBlockPos(); - BlockPos nearestChest = null; + BlockPos nearest = null; double nearestDistance = Double.MAX_VALUE; - - for (BlockPos pos : BlockPos.iterate( - playerPos.add(-16, -8, -16), - playerPos.add(16, 8, 16))) { - + for (BlockPos pos : BlockPos.iterate(mc.player.getBlockPos().add(-16, -8, -16), mc.player.getBlockPos().add(16, 8, 16))) { if (mc.world.getBlockState(pos).getBlock() == Blocks.ENDER_CHEST) { - double distance = pos.getSquaredDistance(mc.player.getPos()); - if (distance < nearestDistance) { - nearestDistance = distance; - nearestChest = pos.toImmutable(); - } + double dist = pos.getSquaredDistance(mc.player.getPos()); + if (dist < nearestDistance) { nearestDistance = dist; nearest = pos.toImmutable(); } } } - - return nearestChest; + return nearest; } private void moveTowardsBlock(BlockPos target) { - Vec3d playerPos = mc.player.getPos(); - Vec3d targetPos = Vec3d.ofCenter(target); - Vec3d direction = targetPos.subtract(playerPos).normalize(); - - double yaw = Math.toDegrees(Math.atan2(-direction.x, direction.z)); - mc.player.setYaw((float) yaw); - - KeyBinding.setKeyPressed(mc.options.forwardKey.getDefaultKey(), true); + Vec3d dir = Vec3d.ofCenter(target).subtract(mc.player.getPos()).normalize(); + mc.player.setYaw((float) Math.toDegrees(Math.atan2(-dir.x, dir.z))); + mc.options.forwardKey.setPressed(true); } private void handleOpeningChest() { - if (targetChest == null) { - currentState = State.GOING_TO_CHEST; - return; - } - - if (sneaking) { - } - mc.options.sneakKey.setPressed(false); - - KeyBinding.setKeyPressed(mc.options.forwardKey.getDefaultKey(), false); - KeyBinding.setKeyPressed(mc.options.jumpKey.getDefaultKey(), true); + if (targetChest == null) { changeState(State.GOING_TO_CHEST); return; } - if (chestOpenAttempts < 20) { - lookAtBlock(targetChest); + if (stateTick % 5 == 0 && mc.interactionManager != null) { + if (debug.get()) info("DEBUG: Right-clicking ender chest... (attempt " + ((stateTick / 5) + 1) + ")"); + mc.interactionManager.interactBlock(mc.player, Hand.MAIN_HAND, new BlockHitResult(Vec3d.ofCenter(targetChest), Direction.UP, targetChest, false)); } - if (chestOpenAttempts % 5 == 0) { - if (mc.interactionManager != null && mc.player != null) { - mc.interactionManager.interactBlock( - mc.player, - Hand.MAIN_HAND, - new BlockHitResult( - Vec3d.ofCenter(targetChest), - Direction.UP, - targetChest, - false)); - if (notifications.get()) - info("Right-clicking ender chest... (attempt " + (chestOpenAttempts / 5 + 1) + ")"); - } - } - - chestOpenAttempts++; - if (mc.player.currentScreenHandler instanceof GenericContainerScreenHandler) { - KeyBinding.setKeyPressed(mc.options.jumpKey.getDefaultKey(), false); - currentState = State.DEPOSITING_ITEMS; - lastProcessedSlot = -1; - tickCounter = 0; - if (notifications.get()) - info("Ender chest opened successfully! Made by GLZD "); - } - - if (chestOpenAttempts > 200) { - KeyBinding.setKeyPressed(mc.options.jumpKey.getDefaultKey(), false); - if (notifications.get()) - ChatUtils.error("Failed to open ender chest after multiple attempts!"); - currentState = State.DISCONNECTING; + if (debug.get()) info("DEBUG: Ender chest opened successfully!"); + changeState(State.DEPOSITING_ITEMS); + } else if (stateTick > 200) { + if (debug.get()) error("DEBUG: Failed to open chest after max attempts!"); + changeState(State.DISCONNECTING); } } + // ── Inventory Transfer Logic ────────────────────────────────────────── private void handleDepositingItems() { - if (!depositToEChest.get()) { - currentState = State.DISCONNECTING; - if (notifications.get()) - info("Deposit to ender chest disabled, skipping deposit."); + if (!(mc.player.currentScreenHandler instanceof GenericContainerScreenHandler handler)) { + if (debug.get() && stateTick % 20 == 0) info("DEBUG: Lost chest handler. Waiting before retry..."); + if (stateTick % 20 == 0) { + changeState(State.OPENING_CHEST); + } return; } - mc.options.sneakKey.setPressed(false); - - if (mc.player.currentScreenHandler instanceof GenericContainerScreenHandler) { - GenericContainerScreenHandler handler = (GenericContainerScreenHandler) mc.player.currentScreenHandler; - - if (!hasItemsToDeposit()) { - itemsDepositedSuccessfully = true; - if (notifications.get()) - info("All items deposited successfully!"); - mc.player.closeHandledScreen(); - transferDelayCounter = 10; - currentState = State.DISCONNECTING; - return; - } - - transferItemsToChest(handler); - - } else { - currentState = State.OPENING_CHEST; - chestOpenAttempts = 0; - } + transferItemsToChest(handler); - if (tickCounter > 900) { - if (notifications.get()) - ChatUtils.error("Timed out depositing items!"); - currentState = State.DISCONNECTING; + if (stateTick > 900) { + if (debug.get()) error("DEBUG: Timed out depositing items!"); + changeState(State.DISCONNECTING); } } - private boolean isVitalItem(ItemStack stack) { - if (stack.isEmpty() || stack.getItem() == Items.AIR) - return true; - - // Check if item is in the blacklist setting - if (depositBlacklist.get().contains(stack.getItem())) - return true; - - // Silk touch pickaxes are no longer vital so they can be deposited at the end - - if (stack.getItem() == Items.ENDER_CHEST) - return true; - - return false; - } - - private boolean hasItemsToDeposit() { - for (int i = 0; i < 36; i++) { - ItemStack stack = mc.player.getInventory().getStack(i); - if (!stack.isEmpty() && !isVitalItem(stack)) { - return true; - } - } - return false; - } - private void transferItemsToChest(GenericContainerScreenHandler handler) { - int totalSlots = handler.slots.size(); - int chestSlots = totalSlots - 36; - int playerInventoryStart = chestSlots; - - // Check if chest is full before depositing + int chestSlots = handler.slots.size() - 36; if (isChestFull(handler, chestSlots)) { - if (notifications.get()) - error("Ender chest is full! Disconnecting for safety."); - currentState = State.DISCONNECTING; + if (debug.get()) error("DEBUG: Ender chest is full!"); + changeState(State.DISCONNECTING); return; } - // Phase 1: Spawners first for (int i = 0; i < 36; i++) { - int slotId = playerInventoryStart + i; + int slotId = chestSlots + i; ItemStack stack = handler.getSlot(slotId).getStack(); - if (!stack.isEmpty() && stack.getItem() == Items.SPAWNER) { - depositSlot(handler, slotId, stack); + clickSlot(handler, slotId, stack); return; } } - // Phase 2: Other non-vital items for (int i = 0; i < 36; i++) { - int slotId = playerInventoryStart + i; + int slotId = chestSlots + i; ItemStack stack = handler.getSlot(slotId).getStack(); - if (!stack.isEmpty() && !isVitalItem(stack)) { - depositSlot(handler, slotId, stack); + clickSlot(handler, slotId, stack); return; } } - if (lastProcessedSlot >= playerInventoryStart) { - lastProcessedSlot = playerInventoryStart - 1; - transferDelayCounter = 3; + ticksSinceLastClick++; + + if (ticksSinceLastClick >= 10) { + itemsDepositedSuccessfully = true; + if (debug.get()) info("DEBUG: All items deposited!"); + mc.player.closeHandledScreen(); + transferDelayCounter = 10; + changeState(State.DISCONNECTING); } } - private void depositSlot(GenericContainerScreenHandler handler, int slotId, ItemStack stack) { - if (notifications.get()) - info("Transferring item from slot " + slotId + ": " + stack.getItem().toString()); - - if (mc.interactionManager != null) { - mc.interactionManager.clickSlot( - handler.syncId, - slotId, - 0, - SlotActionType.QUICK_MOVE, - mc.player); - } + private void clickSlot(GenericContainerScreenHandler handler, int slotId, ItemStack stack) { + if (debug.get()) info("DEBUG: Clicking " + stack.getName().getString() + " in slot " + slotId); + mc.interactionManager.clickSlot(handler.syncId, slotId, 0, SlotActionType.QUICK_MOVE, mc.player); + transferDelayCounter = 3; + ticksSinceLastClick = 0; + } - lastProcessedSlot = slotId; - transferDelayCounter = 2; + private boolean isVitalItem(ItemStack stack) { + if (stack.isEmpty()) return true; + if (depositBlacklist.get().contains(stack.getItem())) return true; + if (stack.getItem() == Items.ENDER_CHEST) return true; + return false; } private boolean isChestFull(GenericContainerScreenHandler handler, int chestSlots) { - // If there's any empty slot, it's not full for (int i = 0; i < chestSlots; i++) { - if (handler.getSlot(i).getStack().isEmpty()) - return false; + if (handler.getSlot(i).getStack().isEmpty()) return false; } - // If all slots are non-empty, we consider it possibly full. - // Note: It might still have stackable space, but requirement says "deconnect if - // full". - // A more robust check would check if any stackable item can fit, but this is - // usually what users mean. return true; } + // ── Disconnect & Webhook ────────────────────────────────────────────── private void handleDisconnecting() { - KeyBinding.setKeyPressed(mc.options.forwardKey.getDefaultKey(), false); - KeyBinding.setKeyPressed(mc.options.jumpKey.getDefaultKey(), false); - + if (debug.get()) info("DEBUG: Disconnecting now..."); sendWebhookNotification(); if (emergencyDisconnect) { - if (notifications.get()) - info("SpawnerProtect: " + emergencyReason + ". Successfully disconnected."); + if (notifications.get()) info("EMERGENCY: " + emergencyReason); } else { - if (notifications.get()) - info("SpawnerProtect: " + detectedPlayer + " detected. Successfully disconnected."); + if (notifications.get()) info("Protection complete. Disconnecting..."); } - if (mc.world != null) { - mc.world.disconnect(); - } - - if (notifications.get()) - info("Disconnected due to player detection."); - toggle(); + if (mc.world != null) mc.world.disconnect(); + this.toggle(); } - private void sendWebhookNotification() { - if (!webhook.get() || webhookUrl.get() == null || webhookUrl.get().trim().isEmpty()) { - if (notifications.get()) - info("Webhook disabled or URL not configured."); - return; - } - - String webhookUrlValue = webhookUrl.get().trim(); - - long discordTimestamp = detectionTime / 1000L; - - String messageContent = ""; - if (selfPing.get() && discordId.get() != null && !discordId.get().trim().isEmpty()) { - messageContent = String.format("<@%s>", discordId.get().trim()); + private void disableAutoReconnectIfEnabled() { + if (!disableAutoReconnect.get()) return; + Module autoReconnect = Modules.get().get(AutoReconnect.class); + if (autoReconnect != null && autoReconnect.isActive()) { + autoReconnect.toggle(); + if (debug.get()) info("DEBUG: Disabled AutoReconnect."); } - - String embedJson = createWebhookPayload(messageContent, discordTimestamp); - - new Thread(() -> { - try { - HttpClient client = HttpClient.newHttpClient(); - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(webhookUrlValue)) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(embedJson)) - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() >= 200 && response.statusCode() < 300) { - if (notifications.get()) - info("Webhook notification sent successfully!"); - } else { - if (notifications.get()) - ChatUtils.error("Failed to send webhook notification. Status: " + response.statusCode()); - } - } catch (Exception e) { - if (notifications.get()) - ChatUtils.error("Failed to send webhook notification: " + e.getMessage()); - } - }).start(); } - private String createWebhookPayload(String messageContent, long discordTimestamp) { - String title = emergencyDisconnect ? "SpawnerProtect Emergency Alert" : "SpawnerProtect Alert"; - String description; - - if (emergencyDisconnect) { - description = String.format( - "**Player Detected:** %s\\n**Detection Time:** \\n**Reason:** %s\\n**Disconnected:** Yes", - escapeJson(detectedPlayer), discordTimestamp, escapeJson(emergencyReason)); - } else { - description = String.format( - "**Player Detected:** %s\\n**Detection Time:** \\n**Spawners Mined:** %s\\n**Items Deposited:** %s\\n**Disconnected:** Yes", - escapeJson(detectedPlayer), discordTimestamp, - spawnersMinedSuccessfully ? "✅ Success" : "❌ Failed", - itemsDepositedSuccessfully ? "✅ Success" : "❌ Failed"); - } + private void sendWebhookNotification() { + if (!webhook.get() || webhookUrl.get() == null || webhookUrl.get().trim().isEmpty()) return; + String url = webhookUrl.get().trim(); + long discordTimestamp = detectionTime / 1000L; - int color = emergencyDisconnect ? 16711680 : 16766720; + String messageContent = (selfPing.get() && discordId.get() != null && !discordId.get().trim().isEmpty()) + ? String.format("<@%s>", discordId.get().trim()) : ""; - return String.format(""" + String payload = String.format(""" { "username": "Glazed Webhook", "avatar_url": "https://i.imgur.com/OL2y1cr.png", @@ -1015,32 +636,29 @@ private String createWebhookPayload(String messageContent, long discordTimestamp "description": "%s", "color": %d, "timestamp": "%s", - "footer": { - "text": "Sent by Glazed" - } + "footer": { "text": "Sent by Glazed" } }] }""", escapeJson(messageContent), - title, - description, - color, - Instant.now().toString()); + emergencyDisconnect ? "SpawnerProtect Emergency" : "SpawnerProtect Alert", + escapeJson(emergencyDisconnect + ? String.format("**Player:** %s\\n**Reason:** %s", detectedPlayer, emergencyReason) + : String.format("**Player:** %s\\n**Spawners:** %s\\n**Deposited:** %s", detectedPlayer, spawnersMinedSuccessfully ? "✅" : "❌", itemsDepositedSuccessfully ? "✅" : "❌")), + emergencyDisconnect ? 16711680 : 16766720, + Instant.now().toString() + ); + + Thread.startVirtualThread(() -> { + try { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(payload)).build(); + client.send(request, HttpResponse.BodyHandlers.ofString()); + } catch (Exception ignored) {} + }); } private String escapeJson(String input) { - if (input == null) - return ""; - return input.replace("\\", "\\\\") - .replace("\"", "\\\"") - .replace("\n", "\\n") - .replace("\r", "\\r") - .replace("\t", "\\t"); - } - - @Override - public void onDeactivate() { - stopBreaking(); - KeyBinding.setKeyPressed(mc.options.forwardKey.getDefaultKey(), false); - KeyBinding.setKeyPressed(mc.options.jumpKey.getDefaultKey(), false); + if (input == null) return ""; + return input.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r"); } } \ No newline at end of file diff --git a/src/main/java/com/nnpg/glazed/modules/pvp/FastXP.java b/src/main/java/com/nnpg/glazed/modules/pvp/FastXP.java new file mode 100644 index 00000000..4e0f14a7 --- /dev/null +++ b/src/main/java/com/nnpg/glazed/modules/pvp/FastXP.java @@ -0,0 +1,77 @@ +package com.nnpg.glazed.modules.pvp; + +import com.nnpg.glazed.GlazedAddon; +import meteordevelopment.meteorclient.events.world.TickEvent; +import meteordevelopment.meteorclient.settings.BoolSetting; +import meteordevelopment.meteorclient.settings.Setting; +import meteordevelopment.meteorclient.settings.SettingGroup; +import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.utils.player.ChatUtils; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.util.Hand; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; + +import java.util.concurrent.ThreadLocalRandom; + +public class FastXP extends Module { + private final SettingGroup sgGeneral = settings.getDefaultGroup(); + + private final Setting antiPrevention = sgGeneral.add(new BoolSetting.Builder() + .name("anti-prevention") + .description("Skips random ticks to simulate human clicking patterns (avg ~15-18 throws/sec).") + .defaultValue(true) + .build() + ); + + // Zählt runter, wie viele Ticks wir noch pausieren müssen + private int waitTicks = 0; + + public FastXP() { + super(GlazedAddon.CATEGORY, "fast-xp", "Throws XP bottles extremely fast when holding right click."); + } + + @Override + public void onDeactivate() { + waitTicks = 0; // Reset beim Deaktivieren + } + + @EventHandler + private void onTick(TickEvent.Pre event) { + if (mc.player == null || mc.world == null) return; + + // Wenn wir noch in einer "Pause" sind, zähle runter und tue nichts + if (waitTicks > 0) { + waitTicks--; + return; + } + + // Prüfe ob rechte Maustaste gedrückt ist + if (!mc.options.useKey.isPressed()) return; + + ItemStack mainHand = mc.player.getMainHandStack(); + ItemStack offHand = mc.player.getOffHandStack(); + + if (mainHand.isOf(Items.EXPERIENCE_BOTTLE) || offHand.isOf(Items.EXPERIENCE_BOTTLE)) { + Hand handToUse = mainHand.isOf(Items.EXPERIENCE_BOTTLE) ? Hand.MAIN_HAND : Hand.OFF_HAND; + + // Wirf die Flasche + mc.interactionManager.interactItem(mc.player, handToUse); + + // Berechne die nächste Pause + if (antiPrevention.get()) { + // 75% Chance: Wirf beim nächsten Tick wieder (50ms Delay) + // 25% Chance: Überspringe einen Tick (100ms Delay) + // -> Durchschnitt: 62,5ms pro Wurf (ca. 16 Würfe/sek) + // Das erzeugt ein extrem natürliches "Klick-Klick---Klick-Klick-Klick" Muster + if (ThreadLocalRandom.current().nextFloat() < 0.25f) { + waitTicks = 1; + } else { + waitTicks = 0; // Keine Pause + } + } else { + waitTicks = 0; // Strikte 20 TPS (50ms) + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/mixins.json b/src/main/resources/mixins.json index 9c4200d9..e4860e00 100644 --- a/src/main/resources/mixins.json +++ b/src/main/resources/mixins.json @@ -6,7 +6,8 @@ "client": [ "HandledScreenMixin", "DefaultSettingsWidgetFactoryAccessor", - "DefaultSettingsWidgetFactoryMixin" + "DefaultSettingsWidgetFactoryMixin", + "MixinClientPlayerInteractionManager" ], "injectors": { "defaultRequire": 1