From dd6c800cd6c2428fd988aabda510c7610715a6c6 Mon Sep 17 00:00:00 2001 From: omtoi101 <83868916+omtoi101@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:34:10 +1000 Subject: [PATCH 01/12] Add files via upload --- updates for hacks/auto_elytra_repair.java | 584 ++++++++++++++++++ updates for hacks/command_updates.java | 64 ++ updates for hacks/config_updates.java | 44 ++ updates for hacks/instructions.txt | 58 ++ updates for hacks/safe_landing_finder.java | 370 +++++++++++ .../stash_hunter_integration.java | 58 ++ updates for hacks/stash_hunter_main.java | 25 + 7 files changed, 1203 insertions(+) create mode 100644 updates for hacks/auto_elytra_repair.java create mode 100644 updates for hacks/command_updates.java create mode 100644 updates for hacks/config_updates.java create mode 100644 updates for hacks/instructions.txt create mode 100644 updates for hacks/safe_landing_finder.java create mode 100644 updates for hacks/stash_hunter_integration.java create mode 100644 updates for hacks/stash_hunter_main.java diff --git a/updates for hacks/auto_elytra_repair.java b/updates for hacks/auto_elytra_repair.java new file mode 100644 index 0000000..5477aae --- /dev/null +++ b/updates for hacks/auto_elytra_repair.java @@ -0,0 +1,584 @@ +package com.stashhunter.stashhunter.modules; + +import com.stashhunter.stashhunter.StashHunter; +import com.stashhunter.stashhunter.utils.Config; +import com.stashhunter.stashhunter.utils.DiscordEmbed; +import com.stashhunter.stashhunter.utils.DiscordWebhook; +import com.stashhunter.stashhunter.utils.ElytraController; +import com.stashhunter.stashhunter.utils.SafeLandingSpotFinder; +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.misc.AutoTool; +import meteordevelopment.meteorclient.systems.modules.player.AutoExp; +import meteordevelopment.meteorclient.systems.modules.movement.elytrafly.ElytraFly; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +import java.util.ArrayList; +import java.util.List; + +public class AutoElytraRepair extends Module { + private final SettingGroup sgGeneral = this.settings.getDefaultGroup(); + + private final Setting repairThreshold = sgGeneral.add(new IntSetting.Builder() + .name("repair-threshold") + .description("Durability threshold to trigger repair (remaining durability).") + .defaultValue(50) + .min(1) + .sliderMax(100) + .build() + ); + + private final Setting landingRadius = sgGeneral.add(new IntSetting.Builder() + .name("landing-radius") + .description("Radius to search for safe landing spots.") + .defaultValue(64) + .min(16) + .sliderMax(128) + .build() + ); + + private final Setting notifyRepairs = sgGeneral.add(new BoolSetting.Builder() + .name("notify-repairs") + .description("Send Discord notifications during repair operations.") + .defaultValue(true) + .build() + ); + + private final Setting repairTimeout = sgGeneral.add(new IntSetting.Builder() + .name("repair-timeout") + .description("Maximum time to spend repairing in seconds.") + .defaultValue(300) + .min(60) + .sliderMax(600) + .build() + ); + + // Repair state + private RepairState currentState = RepairState.MONITORING; + private BlockPos targetLandingSpot = null; + private Vec3d resumePosition = null; + private long repairStartTime = 0; + private int currentRepairSlot = 0; + private List elytraSlots = new ArrayList<>(); + private boolean wasStashHunterActive = false; + private int landingAttempts = 0; + private static final int MAX_LANDING_ATTEMPTS = 3; + + private enum RepairState { + MONITORING, // Normal operation, checking elytra durability + FINDING_LANDING, // Looking for safe landing spot + DESCENDING, // Flying to landing spot + LANDING, // Final landing approach + REPAIRING, // On ground, cycling through elytras for repair + RESUMING, // Taking off and resuming flight + EMERGENCY_DISCONNECT // Critical failure, preparing to disconnect + } + + public AutoElytraRepair() { + super(StashHunter.CATEGORY, "auto-elytra-repair", "Automatically repairs elytras when they get low on durability."); + } + + @Override + public void onActivate() { + currentState = RepairState.MONITORING; + resetRepairState(); + } + + @Override + public void onDeactivate() { + if (currentState != RepairState.MONITORING) { + // Emergency cleanup + resumeNormalOperation(); + } + } + + @EventHandler + private void onTick(TickEvent.Post event) { + if (mc.player == null || mc.world == null) { + return; + } + + switch (currentState) { + case MONITORING: + handleMonitoring(); + break; + case FINDING_LANDING: + handleFindingLanding(); + break; + case DESCENDING: + handleDescending(); + break; + case LANDING: + handleLanding(); + break; + case REPAIRING: + handleRepairing(); + break; + case RESUMING: + handleResuming(); + break; + case EMERGENCY_DISCONNECT: + handleEmergencyDisconnect(); + break; + } + } + + private void handleMonitoring() { + if (!ElytraController.isActive()) { + return; // Only monitor when stash hunter is active + } + + ItemStack chestSlot = mc.player.getEquippedStack(EquipmentSlot.CHEST); + if (chestSlot.getItem() != Items.ELYTRA) { + return; // No elytra equipped + } + + // Check current elytra durability + if (needsRepair(chestSlot)) { + // Find all elytras in inventory + findElytraSlots(); + + if (elytraSlots.isEmpty()) { + // No elytras to repair with - emergency disconnect + error("No elytras found in inventory for repair!"); + initiateEmergencyDisconnect("No backup elytras available"); + return; + } + + // Check if any elytras can be repaired + if (!hasRepairableElytras()) { + error("All elytras are too damaged to repair!"); + initiateEmergencyDisconnect("All elytras beyond repair threshold"); + return; + } + + info("Elytra needs repair (durability: " + (chestSlot.getMaxDamage() - chestSlot.getDamage()) + + "/" + chestSlot.getMaxDamage() + "). Initiating repair sequence."); + + if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { + DiscordEmbed embed = new DiscordEmbed( + "Elytra Repair Initiated", + "Current elytra durability low. Starting repair sequence.\n" + + "Position: " + mc.player.getBlockPos().toShortString() + "\n" + + "Available elytras: " + elytraSlots.size(), + 0xFFAA00 + ); + DiscordWebhook.sendMessage("", embed); + } + + initiateRepairSequence(); + } + } + + private void handleFindingLanding() { + Vec3d playerPos = mc.player.getPos(); + + // Find safe landing spot + BlockPos landingSpot = SafeLandingSpotFinder.findSafeLandingSpot( + playerPos, landingRadius.get(), mc.world + ); + + if (landingSpot != null) { + targetLandingSpot = landingSpot; + resumePosition = playerPos; // Remember where we were + currentState = RepairState.DESCENDING; + info("Found safe landing spot at " + landingSpot.toShortString()); + } else { + landingAttempts++; + if (landingAttempts >= MAX_LANDING_ATTEMPTS) { + error("Could not find safe landing spot after " + MAX_LANDING_ATTEMPTS + " attempts!"); + initiateEmergencyDisconnect("No safe landing spot found"); + return; + } + + // Try expanding search radius + int expandedRadius = landingRadius.get() * (landingAttempts + 1); + info("Expanding landing search radius to " + expandedRadius + " blocks..."); + + // Continue searching with expanded radius next tick + } + } + + private void handleDescending() { + if (targetLandingSpot == null) { + currentState = RepairState.FINDING_LANDING; + return; + } + + Vec3d playerPos = mc.player.getPos(); + double horizontalDistance = Math.sqrt( + Math.pow(targetLandingSpot.getX() - playerPos.x, 2) + + Math.pow(targetLandingSpot.getZ() - playerPos.z, 2) + ); + + // Navigate to landing spot + if (horizontalDistance > 5.0) { + // Fly toward landing spot + flyToPosition(new Vec3d(targetLandingSpot.getX(), playerPos.y, targetLandingSpot.getZ())); + } else { + // Close enough horizontally, start final descent + currentState = RepairState.LANDING; + info("Beginning final landing approach..."); + } + } + + private void handleLanding() { + if (targetLandingSpot == null) { + currentState = RepairState.FINDING_LANDING; + return; + } + + Vec3d playerPos = mc.player.getPos(); + double groundDistance = playerPos.y - (targetLandingSpot.getY() + 1); + + if (mc.player.isOnGround()) { + // Successfully landed + info("Successfully landed at " + mc.player.getBlockPos().toShortString()); + currentState = RepairState.REPAIRING; + repairStartTime = System.currentTimeMillis(); + currentRepairSlot = 0; + + // Enable AutoExp module + AutoExp autoExp = Modules.get().get(AutoExp.class); + if (autoExp != null && !autoExp.isActive()) { + autoExp.toggle(); + info("Enabled AutoExp for repair operations"); + } + } else if (groundDistance > 10) { + // Continue descending + Vec3d landingPos = new Vec3d(targetLandingSpot.getX(), targetLandingSpot.getY() + 2, targetLandingSpot.getZ()); + flyToPosition(landingPos); + + // Stop gliding when close to ground for safer landing + if (groundDistance < 5 && mc.player.isGliding()) { + mc.player.stopGliding(); + } + } else { + // Very close to ground, let gravity handle it + if (mc.player.isGliding()) { + mc.player.stopGliding(); + } + } + } + + private void handleRepairing() { + // Check for timeout + if (System.currentTimeMillis() - repairStartTime > repairTimeout.get() * 1000L) { + warning("Repair timeout reached. Resuming flight with current elytra."); + currentState = RepairState.RESUMING; + return; + } + + if (currentRepairSlot >= elytraSlots.size()) { + // Finished cycling through all elytras + info("Completed repair cycle. Selecting best elytra and resuming flight."); + selectBestElytra(); + currentState = RepairState.RESUMING; + return; + } + + // Get current elytra in repair slot + int slotIndex = elytraSlots.get(currentRepairSlot); + ItemStack elytra = mc.player.getInventory().getStack(slotIndex); + + if (elytra.getItem() != Items.ELYTRA) { + // Elytra was moved or consumed, skip this slot + currentRepairSlot++; + return; + } + + // Move elytra to offhand for repair + if (!isElytraInOffhand(elytra)) { + moveElytraToOffhand(slotIndex); + info("Moved elytra to offhand for repair (slot " + slotIndex + ")"); + } + + // Check if this elytra is fully repaired or good enough + if (!needsRepair(elytra)) { + info("Elytra in slot " + slotIndex + " is sufficiently repaired"); + currentRepairSlot++; + + // Wait a bit before moving to next elytra + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + private void handleResuming() { + // Disable AutoExp + AutoExp autoExp = Modules.get().get(AutoExp.class); + if (autoExp != null && autoExp.isActive()) { + autoExp.toggle(); + info("Disabled AutoExp after repair completion"); + } + + // Ensure best elytra is equipped + selectBestElytra(); + + // Take off and resume flight + if (mc.player.isOnGround()) { + // Jump to start takeoff + mc.options.jumpKey.setPressed(true); + + // After a brief delay, start elytra flight + new Thread(() -> { + try { + Thread.sleep(500); // Wait for jump + mc.options.jumpKey.setPressed(false); + + Thread.sleep(1000); // Wait to gain some altitude + + if (mc.player.getEquippedStack(EquipmentSlot.CHEST).getItem() == Items.ELYTRA) { + mc.player.startGliding(); + info("Restarted elytra gliding"); + } + + Thread.sleep(2000); // Wait for stable flight + + // Re-enable ElytraFly module + ElytraFly elytraFly = Modules.get().get(ElytraFly.class); + if (elytraFly != null && !elytraFly.isActive()) { + elytraFly.toggle(); + info("Re-enabled ElytraFly module"); + } + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }).start(); + } + + // Resume stash hunter if it was active + if (wasStashHunterActive && !ElytraController.isActive()) { + // ElytraController will resume automatically when conditions are right + info("Repair sequence complete. Stash hunter will resume automatically."); + } + + if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { + DiscordEmbed embed = new DiscordEmbed( + "Elytra Repair Complete", + "Successfully repaired elytras and resumed flight.\n" + + "Position: " + mc.player.getBlockPos().toShortString() + "\n" + + "Status: Resuming stash hunting operations", + 0x00FF00 + ); + DiscordWebhook.sendMessage("", embed); + } + + resetRepairState(); + currentState = RepairState.MONITORING; + } + + private void handleEmergencyDisconnect() { + error("Emergency disconnect initiated. Saving state and disconnecting..."); + + // Save current trip state + if (ElytraController.isActive()) { + ElytraController.pause(); + } + + if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { + DiscordEmbed embed = new DiscordEmbed( + "Emergency Disconnect", + "Critical elytra repair failure. Bot is disconnecting for safety.\n" + + "Position: " + (mc.player != null ? mc.player.getBlockPos().toShortString() : "Unknown") + "\n" + + "Reason: Unable to repair elytras safely", + 0xFF0000 + ); + DiscordWebhook.sendMessage("@everyone", embed); + } + + // Disconnect from server + if (mc.getNetworkHandler() != null) { + mc.getNetworkHandler().getConnection().disconnect( + net.minecraft.text.Text.of("Emergency disconnect: Elytra repair failed") + ); + } + + this.toggle(); // Disable the module + } + + private void initiateRepairSequence() { + wasStashHunterActive = ElytraController.isActive(); + + // Pause stash hunter + if (ElytraController.isActive()) { + ElytraController.pause(); + info("Paused stash hunter for elytra repair"); + } + + currentState = RepairState.FINDING_LANDING; + landingAttempts = 0; + } + + private void initiateEmergencyDisconnect(String reason) { + error("Initiating emergency disconnect: " + reason); + currentState = RepairState.EMERGENCY_DISCONNECT; + } + + private boolean needsRepair(ItemStack elytra) { + if (elytra.getItem() != Items.ELYTRA) return false; + int remainingDurability = elytra.getMaxDamage() - elytra.getDamage(); + return remainingDurability <= repairThreshold.get(); + } + + private void findElytraSlots() { + elytraSlots.clear(); + + for (int i = 0; i < mc.player.getInventory().size(); i++) { + ItemStack stack = mc.player.getInventory().getStack(i); + if (stack.getItem() == Items.ELYTRA) { + elytraSlots.add(i); + } + } + + info("Found " + elytraSlots.size() + " elytras in inventory"); + } + + private boolean hasRepairableElytras() { + for (int slot : elytraSlots) { + ItemStack elytra = mc.player.getInventory().getStack(slot); + if (elytra.getItem() == Items.ELYTRA) { + int remainingDurability = elytra.getMaxDamage() - elytra.getDamage(); + // Can be repaired if it has at least some durability and can benefit from mending + if (remainingDurability > 10 && elytra.getDamage() > 0) { + return true; + } + } + } + return false; + } + + private boolean isElytraInOffhand(ItemStack targetElytra) { + ItemStack offhand = mc.player.getInventory().getStack(40); // Offhand slot + return offhand.getItem() == Items.ELYTRA && offhand.getDamage() == targetElytra.getDamage(); + } + + private void moveElytraToOffhand(int fromSlot) { + // Click on the elytra in inventory + mc.interactionManager.clickSlot( + mc.player.currentScreenHandler.syncId, + fromSlot < 9 ? fromSlot + 36 : fromSlot, // Convert to screen handler slot + 0, + SlotActionType.PICKUP, + mc.player + ); + + // Click on offhand slot + mc.interactionManager.clickSlot( + mc.player.currentScreenHandler.syncId, + 45, // Offhand slot in screen handler + 0, + SlotActionType.PICKUP, + mc.player + ); + } + + private void selectBestElytra() { + ItemStack bestElytra = null; + int bestSlot = -1; + int bestDurability = 0; + + // Find elytra with highest durability + for (int slot : elytraSlots) { + ItemStack elytra = mc.player.getInventory().getStack(slot); + if (elytra.getItem() == Items.ELYTRA) { + int durability = elytra.getMaxDamage() - elytra.getDamage(); + if (durability > bestDurability) { + bestDurability = durability; + bestElytra = elytra; + bestSlot = slot; + } + } + } + + if (bestElytra != null && bestSlot != -1) { + // Equip the best elytra + equipElytra(bestSlot); + info("Equipped elytra with " + bestDurability + " durability"); + } + } + + private void equipElytra(int slot) { + // Move elytra to chest slot + mc.interactionManager.clickSlot( + mc.player.currentScreenHandler.syncId, + slot < 9 ? slot + 36 : slot, + 0, + SlotActionType.PICKUP, + mc.player + ); + + mc.interactionManager.clickSlot( + mc.player.currentScreenHandler.syncId, + 6, // Chest slot in screen handler + 0, + SlotActionType.PICKUP, + mc.player + ); + } + + private void flyToPosition(Vec3d target) { + if (mc.player == null) return; + + Vec3d playerPos = mc.player.getPos(); + double dx = target.x - playerPos.x; + double dz = target.z - playerPos.z; + double dy = target.y - playerPos.y; + + double horizontalDistance = Math.sqrt(dx * dx + dz * dz); + + if (horizontalDistance > 1.0) { + double yaw = Math.atan2(dz, dx) * 180.0 / Math.PI - 90.0; + double pitch = Math.atan2(dy, horizontalDistance) * 180.0 / Math.PI; + + // Limit pitch for safe flying + pitch = Math.max(-30.0, Math.min(30.0, pitch)); + + mc.player.setYaw((float) yaw); + mc.player.setPitch((float) pitch); + } + } + + private void resetRepairState() { + targetLandingSpot = null; + resumePosition = null; + repairStartTime = 0; + currentRepairSlot = 0; + elytraSlots.clear(); + wasStashHunterActive = false; + landingAttempts = 0; + } + + private void resumeNormalOperation() { + resetRepairState(); + currentState = RepairState.MONITORING; + + // Re-enable ElytraFly if needed + ElytraFly elytraFly = Modules.get().get(ElytraFly.class); + if (elytraFly != null && !elytraFly.isActive() && wasStashHunterActive) { + elytraFly.toggle(); + } + } + + public RepairState getCurrentState() { + return currentState; + } + + public boolean isRepairing() { + return currentState != RepairState.MONITORING; + } +} \ No newline at end of file diff --git a/updates for hacks/command_updates.java b/updates for hacks/command_updates.java new file mode 100644 index 0000000..00215c3 --- /dev/null +++ b/updates for hacks/command_updates.java @@ -0,0 +1,64 @@ +// Add these imports to StashHunterCommand.java: +import com.stashhunter.stashhunter.modules.AutoElytraRepair; +import meteordevelopment.meteorclient.systems.modules.Modules; + +// Add this new command to the build() method in StashHunterCommand.java: + +// Repair status command +builder.then(literal("repair") + .executes(context -> { + AutoElytraRepair repairModule = Modules.get().get(AutoElytraRepair.class); + if (repairModule == null) { + error("Auto Elytra Repair module not found."); + return SINGLE_SUCCESS; + } + + if (!repairModule.isActive()) { + info("Auto Elytra Repair: §cDisabled"); + } else if (repairModule.isRepairing()) { + info("Auto Elytra Repair: §eActive - " + repairModule.getCurrentState().toString()); + } else { + info("Auto Elytra Repair: §aEnabled - Monitoring"); + } + + // Show current elytra status + if (MeteorClient.mc.player != null) { + ItemStack chestSlot = MeteorClient.mc.player.getEquippedStack(EquipmentSlot.CHEST); + if (chestSlot.getItem() == Items.ELYTRA) { + int durability = chestSlot.getMaxDamage() - chestSlot.getDamage(); + int maxDurability = chestSlot.getMaxDamage(); + info("Current Elytra: " + durability + "/" + maxDurability + " durability"); + } else { + info("No Elytra currently equipped"); + } + } + + return SINGLE_SUCCESS; + }) + .then(literal("toggle") + .executes(context -> { + AutoElytraRepair repairModule = Modules.get().get(AutoElytraRepair.class); + if (repairModule == null) { + error("Auto Elytra Repair module not found."); + return SINGLE_SUCCESS; + } + + repairModule.toggle(); + info("Auto Elytra Repair: " + (repairModule.isActive() ? "§aEnabled" : "§cDisabled")); + return SINGLE_SUCCESS; + }) + ) +); + +// Update the help command to include repair information: +// In the help command execution, add these lines: + +info("§7/stashhunter repair §f- Show elytra repair status"); +info("§7/stashhunter repair toggle §f- Toggle auto elytra repair"); + +// Add to the detailed help section: +info(""); +info("§eAuto Elytra Repair:"); +info("§7Automatically repairs elytras when durability gets low"); +info("§7Finds safe landing spots and cycles through elytras for mending"); +info("§7Requires AutoExp module to be available in Meteor Client"); \ No newline at end of file diff --git a/updates for hacks/config_updates.java b/updates for hacks/config_updates.java new file mode 100644 index 0000000..6095e6d --- /dev/null +++ b/updates for hacks/config_updates.java @@ -0,0 +1,44 @@ +// Add these new configuration variables to Config.java + +// Auto Elytra Repair settings +public static int autoRepairThreshold = 50; +public static int autoRepairLandingRadius = 64; +public static boolean autoRepairNotifications = true; +public static int autoRepairTimeout = 300; + +// Add these to the load() method: +autoRepairThreshold = getIntProperty("autoRepairThreshold", 50); +autoRepairLandingRadius = getIntProperty("autoRepairLandingRadius", 64); +autoRepairNotifications = getBoolProperty("autoRepairNotifications", true); +autoRepairTimeout = getIntProperty("autoRepairTimeout", 300); + +// Add these to the save() method: +properties.setProperty("autoRepairThreshold", String.valueOf(autoRepairThreshold)); +properties.setProperty("autoRepairLandingRadius", String.valueOf(autoRepairLandingRadius)); +properties.setProperty("autoRepairNotifications", String.valueOf(autoRepairNotifications)); +properties.setProperty("autoRepairTimeout", String.valueOf(autoRepairTimeout)); + +// Add validation in the validate() method: +if (autoRepairThreshold < 1) { + autoRepairThreshold = 1; + changed = true; +} else if (autoRepairThreshold > 100) { + autoRepairThreshold = 100; + changed = true; +} + +if (autoRepairLandingRadius < 16) { + autoRepairLandingRadius = 16; + changed = true; +} else if (autoRepairLandingRadius > 128) { + autoRepairLandingRadius = 128; + changed = true; +} + +if (autoRepairTimeout < 60) { + autoRepairTimeout = 60; + changed = true; +} else if (autoRepairTimeout > 600) { + autoRepairTimeout = 600; + changed = true; +} \ No newline at end of file diff --git a/updates for hacks/instructions.txt b/updates for hacks/instructions.txt new file mode 100644 index 0000000..0fb8a98 --- /dev/null +++ b/updates for hacks/instructions.txt @@ -0,0 +1,58 @@ +Installation Instructions +Here's how to integrate the auto-repair plugin into your current Stash Hunter code: +1. New Files to Create +Create these new files in your project: + +src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java (from first artifact) +src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java (from second artifact) + +2. Existing Files to Update +Update StashHunterModule.java: + +Add the import and integration code from the third artifact +This replaces the existing elytra checking logic with enhanced logic that works with the repair system + +Update StashHunter.java (main class): + +Add the module registration code from the fourth artifact +This registers the new AutoElytraRepair module with Meteor + +Update Config.java: + +Add the configuration variables and validation from the fifth artifact +This adds settings for repair thresholds and timeouts + +Update StashHunterCommand.java: + +Add the new repair command from the sixth artifact +This adds /stashhunter repair commands for monitoring and control + +3. How the Auto-Repair System Works + +Monitoring: Continuously checks elytra durability while stash hunting is active +Safe Landing: When durability drops below threshold, finds the safest landing spot within radius +Landing Process: Safely descends and lands, avoiding hazards like water, lava, or confined spaces +Repair Cycle: Activates AutoExp module and cycles through all elytras in inventory for mending +Resume Flight: Equips the best repaired elytra and resumes stash hunting + +4. Configuration Options +The system adds these settings: + +Repair Threshold: Durability level to trigger repair (default: 50) +Landing Radius: Search radius for safe spots (default: 64 blocks) +Repair Timeout: Maximum repair time (default: 300 seconds) +Discord Notifications: Alert on Discord during repairs (default: true) + +5. Safety Features + +Hazard Avoidance: Won't land in water, lava, or near dangerous blocks +Takeoff Clearance: Ensures sufficient overhead space for safe takeoff +Emergency Disconnect: If no safe landing spot found or all elytras are beyond repair +Progress Persistence: Saves trip progress before landing, resumes after repair + +6. Commands + +/stashhunter repair - Show current repair status +/stashhunter repair toggle - Enable/disable auto-repair system + +The system integrates seamlessly with your existing Stash Hunter and will automatically handle elytra repairs without interrupting the scanning process, making your bot much more autonomous and reliable for long scanning operations. \ No newline at end of file diff --git a/updates for hacks/safe_landing_finder.java b/updates for hacks/safe_landing_finder.java new file mode 100644 index 0000000..a38d8ac --- /dev/null +++ b/updates for hacks/safe_landing_finder.java @@ -0,0 +1,370 @@ +package com.stashhunter.stashhunter.utils; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.HashSet; +import java.util.Set; + +public class SafeLandingSpotFinder { + + // Blocks that are safe to land on + private static final Set SAFE_LANDING_BLOCKS = Set.of( + Blocks.GRASS_BLOCK, + Blocks.DIRT, + Blocks.STONE, + Blocks.COBBLESTONE, + Blocks.SAND, + Blocks.GRAVEL, + Blocks.NETHERRACK, + Blocks.END_STONE, + Blocks.OBSIDIAN, + Blocks.DEEPSLATE, + Blocks.ANDESITE, + Blocks.DIORITE, + Blocks.GRANITE, + Blocks.SANDSTONE, + Blocks.RED_SANDSTONE + ); + + // Blocks that are hazardous to land on or near + private static final Set HAZARDOUS_BLOCKS = Set.of( + Blocks.LAVA, + Blocks.WATER, + Blocks.CACTUS, + Blocks.SWEET_BERRY_BUSH, + Blocks.WITHER_ROSE, + Blocks.FIRE, + Blocks.SOUL_FIRE, + Blocks.MAGMA_BLOCK, + Blocks.CAMPFIRE, + Blocks.SOUL_CAMPFIRE + ); + + // Blocks that would cause head bumping when taking off + private static final Set OVERHEAD_HAZARDS = Set.of( + Blocks.STONE, + Blocks.COBBLESTONE, + Blocks.DEEPSLATE, + Blocks.ANDESITE, + Blocks.DIORITE, + Blocks.GRANITE, + Blocks.OBSIDIAN, + Blocks.BEDROCK, + Blocks.NETHERRACK, + Blocks.END_STONE, + Blocks.SANDSTONE, + Blocks.RED_SANDSTONE, + Blocks.DIRT, + Blocks.GRASS_BLOCK + ); + + /** + * Finds a safe landing spot within the specified radius + * @param startPos Current player position + * @param radius Search radius in blocks + * @param world The world to search in + * @return A safe landing position, or null if none found + */ + public static BlockPos findSafeLandingSpot(Vec3d startPos, int radius, ClientWorld world) { + if (world == null) return null; + + BlockPos centerPos = BlockPos.ofFloored(startPos); + + // Start from current position and spiral outward + for (int r = 0; r <= radius; r += 8) { // Check every 8 blocks for performance + for (int x = -r; x <= r; x += 8) { + for (int z = -r; z <= r; z += 8) { + // Only check positions roughly on the circle edge for this radius + double distance = Math.sqrt(x * x + z * z); + if (distance < r - 4 || distance > r + 4) continue; + + BlockPos checkPos = centerPos.add(x, 0, z); + BlockPos safeSpot = findSafeLandingAtColumn(checkPos, world); + + if (safeSpot != null) { + return safeSpot; + } + } + } + } + + return null; // No safe spot found + } + + /** + * Finds a safe landing spot in a vertical column + * @param columnPos The X,Z position to check vertically + * @param world The world to search in + * @return A safe landing position in this column, or null if none found + */ + private static BlockPos findSafeLandingAtColumn(BlockPos columnPos, ClientWorld world) { + // Start from a reasonable height and work down + int maxY = Math.min(world.getHeight() - 1, columnPos.getY() + 50); + int minY = Math.max(world.getBottomY(), columnPos.getY() - 100); + + for (int y = maxY; y >= minY; y--) { + BlockPos landingPos = new BlockPos(columnPos.getX(), y, columnPos.getZ()); + + if (isSafeLandingSpot(landingPos, world)) { + return landingPos; + } + } + + return null; + } + + /** + * Checks if a specific position is safe for landing + * @param pos The position to check + * @param world The world context + * @return true if safe to land here + */ + public static boolean isSafeLandingSpot(BlockPos pos, ClientWorld world) { + try { + // Check the landing block itself + BlockState landingBlock = world.getBlockState(pos); + if (!SAFE_LANDING_BLOCKS.contains(landingBlock.getBlock()) || !landingBlock.isFullCube(world, pos)) { + return false; + } + + // Check that the two blocks above are air (space for player) + BlockPos abovePos1 = pos.up(); + BlockPos abovePos2 = pos.up(2); + + if (!world.getBlockState(abovePos1).isAir() || !world.getBlockState(abovePos2).isAir()) { + return false; + } + + // Check for overhead hazards that would block takeoff (up to 10 blocks above) + for (int i = 3; i <= 10; i++) { + BlockPos overheadPos = pos.up(i); + BlockState overheadBlock = world.getBlockState(overheadPos); + + if (OVERHEAD_HAZARDS.contains(overheadBlock.getBlock()) && overheadBlock.isFullCube(world, overheadPos)) { + return false; // Would hit head during takeoff + } + } + + // Check surrounding area for hazards (3x3 area) + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + BlockPos surroundingPos = pos.add(dx, 0, dz); + BlockState surroundingBlock = world.getBlockState(surroundingPos); + + if (HAZARDOUS_BLOCKS.contains(surroundingBlock.getBlock())) { + return false; + } + + // Also check one block above surrounding area for hanging hazards + BlockPos aboveSurrounding = surroundingPos.up(); + BlockState aboveSurroundingBlock = world.getBlockState(aboveSurrounding); + + if (HAZARDOUS_BLOCKS.contains(aboveSurroundingBlock.getBlock())) { + return false; + } + } + } + + // Check that we're not landing in a confined space (check 5x5 area for walls) + int wallCount = 0; + for (int dx = -2; dx <= 2; dx++) { + for (int dz = -2; dz <= 2; dz++) { + if (dx == 0 && dz == 0) continue; // Skip center position + + BlockPos wallCheckPos = pos.add(dx, 1, dz); // Check at player height + BlockState wallBlock = world.getBlockState(wallCheckPos); + + if (!wallBlock.isAir() && wallBlock.isFullCube(world, wallCheckPos)) { + wallCount++; + } + } + } + + // If more than 60% of surrounding area is walled, it's too confined + if (wallCount > 15) { // 60% of 24 surrounding blocks + return false; + } + + // Additional safety checks for specific biomes/situations + + // Check if we're above void (in End or below bedrock) + if (pos.getY() < world.getBottomY() + 5) { + return false; + } + + // Check if landing spot has solid ground beneath (not floating) + boolean hasGroundSupport = false; + for (int i = 1; i <= 5; i++) { + BlockPos belowPos = pos.down(i); + BlockState belowBlock = world.getBlockState(belowPos); + + if (!belowBlock.isAir() && belowBlock.isFullCube(world, belowPos)) { + hasGroundSupport = true; + break; + } + } + + if (!hasGroundSupport) { + return false; // Floating platform, not safe + } + + return true; // All checks passed + + } catch (Exception e) { + // If we can't check the blocks safely, assume it's not safe + return false; + } + } + + /** + * Evaluates the safety score of a landing spot (higher is better) + * @param pos The position to evaluate + * @param world The world context + * @return Safety score from 0-100, or -1 if not safe at all + */ + public static int evaluateLandingSpotSafety(BlockPos pos, ClientWorld world) { + if (!isSafeLandingSpot(pos, world)) { + return -1; + } + + int safetyScore = 50; // Base score for any safe spot + + try { + // Bonus points for being on natural ground blocks + BlockState landingBlock = world.getBlockState(pos); + if (landingBlock.getBlock() == Blocks.GRASS_BLOCK || + landingBlock.getBlock() == Blocks.STONE || + landingBlock.getBlock() == Blocks.DIRT) { + safetyScore += 10; + } + + // Bonus points for having more open space above + int openSpaceAbove = 0; + for (int i = 3; i <= 15; i++) { + BlockPos abovePos = pos.up(i); + if (world.getBlockState(abovePos).isAir()) { + openSpaceAbove++; + } else { + break; + } + } + safetyScore += Math.min(openSpaceAbove * 2, 20); // Up to 20 bonus points + + // Bonus points for having more open space around + int openSpaceAround = 0; + for (int dx = -3; dx <= 3; dx++) { + for (int dz = -3; dz <= 3; dz++) { + if (dx == 0 && dz == 0) continue; + + BlockPos aroundPos = pos.add(dx, 1, dz); + if (world.getBlockState(aroundPos).isAir()) { + openSpaceAround++; + } + } + } + safetyScore += Math.min(openSpaceAround, 15); // Up to 15 bonus points + + // Penalty for being too high above ground + int distanceToGround = 0; + for (int i = 1; i <= 50; i++) { + BlockPos belowPos = pos.down(i); + BlockState belowBlock = world.getBlockState(belowPos); + + if (!belowBlock.isAir()) { + distanceToGround = i - 1; + break; + } + } + + if (distanceToGround > 10) { + safetyScore -= (distanceToGround - 10) * 2; // Penalty for being too high + } + + return Math.max(0, Math.min(100, safetyScore)); + + } catch (Exception e) { + return 50; // Return base score if evaluation fails + } + } + + /** + * Finds the best landing spot within radius, considering safety scores + * @param startPos Current player position + * @param radius Search radius in blocks + * @param world The world to search in + * @return The best landing position found, or null if none found + */ + public static BlockPos findBestLandingSpot(Vec3d startPos, int radius, ClientWorld world) { + BlockPos bestSpot = null; + int bestScore = -1; + + BlockPos centerPos = BlockPos.ofFloored(startPos); + + // Search in expanding rings for performance + for (int r = 8; r <= radius; r += 8) { + for (int x = -r; x <= r; x += 4) { + for (int z = -r; z <= r; z += 4) { + // Only check positions roughly on the circle edge for this radius + double distance = Math.sqrt(x * x + z * z); + if (distance < r - 4 || distance > r + 4) continue; + + BlockPos checkPos = centerPos.add(x, 0, z); + BlockPos candidate = findSafeLandingAtColumn(checkPos, world); + + if (candidate != null) { + int score = evaluateLandingSpotSafety(candidate, world); + if (score > bestScore) { + bestScore = score; + bestSpot = candidate; + } + + // If we found a really good spot, use it + if (score >= 80) { + return candidate; + } + } + } + } + + // If we found any decent spot in this ring, don't search further + if (bestScore >= 60) { + break; + } + } + + return bestSpot; + } + + /** + * Quick check if the immediate area below is safe for emergency landing + * @param pos Current position + * @param world The world context + * @return true if it's safe to descend here immediately + */ + public static boolean isEmergencyLandingSafe(Vec3d pos, ClientWorld world) { + BlockPos checkPos = BlockPos.ofFloored(pos); + + // Look for ground within reasonable distance below + for (int i = 0; i < 50; i++) { + BlockPos groundPos = checkPos.down(i); + if (isSafeLandingSpot(groundPos, world)) { + return true; + } + + // If we hit a hazardous block, stop checking + BlockState blockState = world.getBlockState(groundPos); + if (HAZARDOUS_BLOCKS.contains(blockState.getBlock())) { + return false; + } + } + + return false; + } +} + \ No newline at end of file diff --git a/updates for hacks/stash_hunter_integration.java b/updates for hacks/stash_hunter_integration.java new file mode 100644 index 0000000..963109c --- /dev/null +++ b/updates for hacks/stash_hunter_integration.java @@ -0,0 +1,58 @@ +// Updates needed for StashHunterModule.java + +// Add this import at the top: +import com.stashhunter.stashhunter.modules.AutoElytraRepair; + +// Add this field with other module references: +private final AutoElytraRepair autoElytraRepair = Modules.get().get(AutoElytraRepair.class); + +// Replace the existing elytra check logic in onTick() method with this enhanced version: + +// Enhanced Elytra check with repair integration +if (ElytraController.isActive()) { + ItemStack chestSlot = mc.player.getEquippedStack(EquipmentSlot.CHEST); + boolean elytraMissing = chestSlot.isEmpty() || + (chestSlot.getItem() == Items.ELYTRA && chestSlot.isDamage() >= chestSlot.getMaxDamage() - 1); + + // Check if auto repair is handling the situation + if (autoElytraRepair != null && autoElytraRepair.isActive() && autoElytraRepair.isRepairing()) { + // Auto repair is active, let it handle elytra management + return; // Skip normal elytra checks + } + + if (elytraMissing) { + wasElytraBroken = true; + elytraBrokenTicks++; + + // Give auto repair system time to activate before panicking + int timeoutTicks = autoElytraRepair != null && autoElytraRepair.isActive() ? 200 : 40; + + if (elytraBrokenTicks > timeoutTicks) { + // Send Discord notification + DiscordEmbed embed = new DiscordEmbed( + "Out of Elytras!", + "The bot has run out of elytras or all elytras are broken beyond repair, and will now disconnect.", + 0xFF0000 + ); + DiscordWebhook.sendMessage("@everyone", embed); + + // Disconnect from server + if (mc.getNetworkHandler() != null) { + mc.getNetworkHandler().getConnection().disconnect(Text.of("Ran out of elytras or all elytras broken.")); + } + + // Stop elytra controller + ElytraController.stop(); + + // Deactivate the module + toggle(); + return; + } + } else { + elytraBrokenTicks = 0; + if (wasElytraBroken) { + wasElytraBroken = false; + reEngagingElytraTicks = 100; // 5 seconds + } + } +} \ No newline at end of file diff --git a/updates for hacks/stash_hunter_main.java b/updates for hacks/stash_hunter_main.java new file mode 100644 index 0000000..4ad55b4 --- /dev/null +++ b/updates for hacks/stash_hunter_main.java @@ -0,0 +1,25 @@ +// Updates needed for StashHunter.java main class + +// Add these imports: +import com.stashhunter.stashhunter.modules.AutoElytraRepair; + +// In the onInitializeClient() method, add the new modules: +@Override +public void onInitializeClient() { + LOG.info("Initializing Stash-Hunter"); + + // Load configuration + Config.load(); + Config.validate(); + + // Add modules + Modules.get().add(new StashHunterModule()); + Modules.get().add(new StuckDetector()); + Modules.get().add(new AltitudeLossDetector()); + Modules.get().add(new AutoElytraRepair()); // Add this line + + // Add commands + Commands.get().add(new StashHunterCommand()); + + LOG.info("Stash-Hunter initialized successfully"); +} \ No newline at end of file From 14430fcda0bf64f31b26c1ffc484487ba8705897 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 03:14:36 +0000 Subject: [PATCH 02/12] feat: Add Auto Elytra Repair module This commit introduces a new module, AutoElytraRepair, which automatically repairs elytras when their durability gets low. The module continuously checks the equipped elytra's durability. When it drops below a configurable threshold, the module finds a safe landing spot, lands the player, and uses experience bottles from the hotbar to repair the elytra. A new command, `/stashhunter repair`, has been added to monitor and control the auto-repair system. A mixin has been added to allow programmatically changing the selected hotbar slot. --- .../stashhunter/stashhunter/StashHunter.java | 2 + .../commands/StashHunterCommand.java | 50 ++ .../mixin/PlayerInventoryAccessor.java | 11 + .../stashhunter/modules/AutoElytraRepair.java | 638 ++++++++++++++++++ .../modules/StashHunterModule.java | 80 ++- .../stashhunter/stashhunter/utils/Config.java | 1 + .../utils/SafeLandingSpotFinder.java | 345 ++++++++++ src/main/resources/addon-template.mixins.json | 3 +- 8 files changed, 1096 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/stashhunter/stashhunter/mixin/PlayerInventoryAccessor.java create mode 100644 src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java create mode 100644 src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java diff --git a/src/main/java/com/stashhunter/stashhunter/StashHunter.java b/src/main/java/com/stashhunter/stashhunter/StashHunter.java index 10b6952..c4f66b8 100644 --- a/src/main/java/com/stashhunter/stashhunter/StashHunter.java +++ b/src/main/java/com/stashhunter/stashhunter/StashHunter.java @@ -10,6 +10,7 @@ import com.stashhunter.stashhunter.modules.NewerNewChunks; import com.stashhunter.stashhunter.modules.StashHunterModule; import com.stashhunter.stashhunter.modules.StuckDetector; +import com.stashhunter.stashhunter.modules.AutoElytraRepair; import com.mojang.logging.LogUtils; import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.addons.GithubRepo; @@ -39,6 +40,7 @@ public void onInitialize() { Modules.get().add(new StuckDetector()); Modules.get().add(new AltitudeLossDetector()); Modules.get().add(new NewerNewChunks()); + Modules.get().add(new AutoElytraRepair()); // Commands Commands.add(new StashHunterCommand()); diff --git a/src/main/java/com/stashhunter/stashhunter/commands/StashHunterCommand.java b/src/main/java/com/stashhunter/stashhunter/commands/StashHunterCommand.java index e8ce964..f3bf820 100644 --- a/src/main/java/com/stashhunter/stashhunter/commands/StashHunterCommand.java +++ b/src/main/java/com/stashhunter/stashhunter/commands/StashHunterCommand.java @@ -1,7 +1,9 @@ package com.stashhunter.stashhunter.commands; +import com.stashhunter.stashhunter.modules.AutoElytraRepair; import com.stashhunter.stashhunter.utils.ElytraController; import com.mojang.brigadier.arguments.IntegerArgumentType; +import meteordevelopment.meteorclient.systems.modules.Modules; import com.mojang.brigadier.arguments.LongArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; @@ -149,6 +151,8 @@ public void build(LiteralArgumentBuilder builder) { info("§7/stashhunter resume [timestamp] §f- Resume the latest or a specific trip"); info("§7/stashhunter list §f- List all saved trips"); info("§7/stashhunter status §f- Show current status and progress"); + info("§7/stashhunter repair §f- Show elytra repair status"); + info("§7/stashhunter repair toggle §f- Toggle auto elytra repair"); info("§7/stashhunter help §f- Show this help message"); info(""); info("§eCoordinate Examples:"); @@ -162,6 +166,52 @@ public void build(LiteralArgumentBuilder builder) { }) ); + // Repair status command + builder.then(literal("repair") + .executes(context -> { + AutoElytraRepair repairModule = Modules.get().get(AutoElytraRepair.class); + if (repairModule == null) { + error("Auto Elytra Repair module not found."); + return SINGLE_SUCCESS; + } + + if (!repairModule.isActive()) { + info("Auto Elytra Repair: §cDisabled"); + } else if (repairModule.isRepairing()) { + info("Auto Elytra Repair: §eActive - " + repairModule.getCurrentStateName()); + } else { + info("Auto Elytra Repair: §aEnabled - Monitoring"); + } + + // Show current elytra status + if (MeteorClient.mc.player != null) { + net.minecraft.item.ItemStack chestSlot = MeteorClient.mc.player.getEquippedStack(net.minecraft.entity.EquipmentSlot.CHEST); + if (chestSlot.getItem() == net.minecraft.item.Items.ELYTRA) { + int durability = chestSlot.getMaxDamage() - chestSlot.getDamage(); + int maxDurability = chestSlot.getMaxDamage(); + info("Current Elytra: " + durability + "/" + maxDurability + " durability"); + } else { + info("No Elytra currently equipped"); + } + } + + return SINGLE_SUCCESS; + }) + .then(literal("toggle") + .executes(context -> { + AutoElytraRepair repairModule = Modules.get().get(AutoElytraRepair.class); + if (repairModule == null) { + error("Auto Elytra Repair module not found."); + return SINGLE_SUCCESS; + } + + repairModule.toggle(); + info("Auto Elytra Repair: " + (repairModule.isActive() ? "§aEnabled" : "§cDisabled")); + return SINGLE_SUCCESS; + }) + ) + ); + // Default help when no arguments builder.executes(context -> { info("Use §7/stashhunter help §ffor command usage."); diff --git a/src/main/java/com/stashhunter/stashhunter/mixin/PlayerInventoryAccessor.java b/src/main/java/com/stashhunter/stashhunter/mixin/PlayerInventoryAccessor.java new file mode 100644 index 0000000..ab65b12 --- /dev/null +++ b/src/main/java/com/stashhunter/stashhunter/mixin/PlayerInventoryAccessor.java @@ -0,0 +1,11 @@ +package com.stashhunter.stashhunter.mixin; + +import net.minecraft.entity.player.PlayerInventory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(PlayerInventory.class) +public interface PlayerInventoryAccessor { + @Accessor("selectedSlot") + void setSelectedSlot(int slot); +} diff --git a/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java b/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java new file mode 100644 index 0000000..924bad9 --- /dev/null +++ b/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java @@ -0,0 +1,638 @@ +package com.stashhunter.stashhunter.modules; + +import com.stashhunter.stashhunter.StashHunter; +import com.stashhunter.stashhunter.utils.Config; +import com.stashhunter.stashhunter.utils.DiscordEmbed; +import com.stashhunter.stashhunter.utils.DiscordWebhook; +import com.stashhunter.stashhunter.utils.ElytraController; +import com.stashhunter.stashhunter.utils.SafeLandingSpotFinder; +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.misc.AutoTool; // Not found in project +// import meteordevelopment.meteorclient.systems.modules.player.AutoExp; // Not found in project +import meteordevelopment.meteorclient.systems.modules.movement.elytrafly.ElytraFly; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket; +import com.stashhunter.stashhunter.mixin.PlayerInventoryAccessor; + +import java.util.ArrayList; +import java.util.List; + +public class AutoElytraRepair extends Module { + private final SettingGroup sgGeneral = this.settings.getDefaultGroup(); + + private final Setting repairThreshold = sgGeneral.add(new IntSetting.Builder() + .name("repair-threshold") + .description("Durability threshold to trigger repair (remaining durability).") + .defaultValue(50) + .min(1) + .sliderMax(100) + .build() + ); + + private final Setting landingRadius = sgGeneral.add(new IntSetting.Builder() + .name("landing-radius") + .description("Radius to search for safe landing spots.") + .defaultValue(64) + .min(16) + .sliderMax(128) + .build() + ); + + private final Setting notifyRepairs = sgGeneral.add(new BoolSetting.Builder() + .name("notify-repairs") + .description("Send Discord notifications during repair operations.") + .defaultValue(true) + .build() + ); + + private final Setting repairTimeout = sgGeneral.add(new IntSetting.Builder() + .name("repair-timeout") + .description("Maximum time to spend repairing in seconds.") + .defaultValue(300) + .min(60) + .sliderMax(600) + .build() + ); + + // Repair state + private RepairState currentState = RepairState.MONITORING; + private BlockPos targetLandingSpot = null; + private Vec3d resumePosition = null; + private long repairStartTime = 0; + private int currentRepairSlot = 0; + private List elytraSlots = new ArrayList<>(); + private boolean wasStashHunterActive = false; + private int landingAttempts = 0; + private static final int MAX_LANDING_ATTEMPTS = 3; + private int timer = 0; + private int resumingStep = 0; + private RepairState nextState = null; + + private enum RepairState { + MONITORING, // Normal operation, checking elytra durability + FINDING_LANDING, // Looking for safe landing spot + DESCENDING, // Flying to landing spot + LANDING, // Final landing approach + REPAIRING, // On ground, cycling through elytras for repair + RESUMING, // Taking off and resuming flight + EMERGENCY_DISCONNECT, // Critical failure, preparing to disconnect + WAITING + } + + public AutoElytraRepair() { + super(StashHunter.CATEGORY, "auto-elytra-repair", "Automatically repairs elytras when they get low on durability."); + } + + @Override + public void onActivate() { + currentState = RepairState.MONITORING; + resetRepairState(); + } + + @Override + public void onDeactivate() { + if (currentState != RepairState.MONITORING) { + // Emergency cleanup + resumeNormalOperation(); + } + } + + @EventHandler + private void onTick(TickEvent.Post event) { + if (mc.player == null || mc.world == null) { + return; + } + + switch (currentState) { + case MONITORING: + handleMonitoring(); + break; + case FINDING_LANDING: + handleFindingLanding(); + break; + case DESCENDING: + handleDescending(); + break; + case LANDING: + handleLanding(); + break; + case REPAIRING: + handleRepairing(); + break; + case RESUMING: + handleResuming(); + break; + case EMERGENCY_DISCONNECT: + handleEmergencyDisconnect(); + break; + case WAITING: + handleWaiting(); + break; + } + } + + private void handleWaiting() { + if (timer > 0) { + timer--; + } else { + if (currentState == RepairState.WAITING && nextState == RepairState.REPAIRING) { + mc.options.useKey.setPressed(false); + } + currentState = nextState; + nextState = null; + } + } + + private void handleMonitoring() { + if (!ElytraController.isActive()) { + return; // Only monitor when stash hunter is active + } + + ItemStack chestSlot = mc.player.getEquippedStack(EquipmentSlot.CHEST); + if (chestSlot.getItem() != Items.ELYTRA) { + return; // No elytra equipped + } + + // Check current elytra durability + if (needsRepair(chestSlot)) { + // Find all elytras in inventory + findElytraSlots(); + + if (elytraSlots.isEmpty()) { + // No elytras to repair with - emergency disconnect + error("No elytras found in inventory for repair!"); + initiateEmergencyDisconnect("No backup elytras available"); + return; + } + + // Check if any elytras can be repaired + if (!hasRepairableElytras()) { + error("All elytras are too damaged to repair!"); + initiateEmergencyDisconnect("All elytras beyond repair threshold"); + return; + } + + info("Elytra needs repair (durability: " + (chestSlot.getMaxDamage() - chestSlot.getDamage()) + + "/" + chestSlot.getMaxDamage() + "). Initiating repair sequence."); + + if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { + DiscordEmbed embed = new DiscordEmbed( + "Elytra Repair Initiated", + "Current elytra durability low. Starting repair sequence.\n" + + "Position: " + mc.player.getBlockPos().toShortString() + "\n" + + "Available elytras: " + elytraSlots.size(), + 0xFFAA00 + ); + DiscordWebhook.sendMessage("", embed); + } + + initiateRepairSequence(); + } + } + + private void handleFindingLanding() { + Vec3d playerPos = mc.player.getPos(); + + // Find safe landing spot + BlockPos landingSpot = SafeLandingSpotFinder.findLandingSpot( + playerPos, landingRadius.get(), mc.world, false + ); + + if (landingSpot != null) { + targetLandingSpot = landingSpot; + resumePosition = playerPos; // Remember where we were + currentState = RepairState.DESCENDING; + info("Found safe landing spot at " + landingSpot.toShortString()); + } else { + landingAttempts++; + if (landingAttempts >= MAX_LANDING_ATTEMPTS) { + error("Could not find safe landing spot after " + MAX_LANDING_ATTEMPTS + " attempts!"); + initiateEmergencyDisconnect("No safe landing spot found"); + return; + } + + // Try expanding search radius + int expandedRadius = landingRadius.get() * (landingAttempts + 1); + info("Expanding landing search radius to " + expandedRadius + " blocks..."); + + // Continue searching with expanded radius next tick + } + } + + private void handleDescending() { + if (targetLandingSpot == null) { + currentState = RepairState.FINDING_LANDING; + return; + } + + Vec3d playerPos = mc.player.getPos(); + double horizontalDistance = Math.sqrt( + Math.pow(targetLandingSpot.getX() - playerPos.x, 2) + + Math.pow(targetLandingSpot.getZ() - playerPos.z, 2) + ); + + // Navigate to landing spot + if (horizontalDistance > 5.0) { + // Fly toward landing spot + flyToPosition(new Vec3d(targetLandingSpot.getX(), playerPos.y, targetLandingSpot.getZ())); + } else { + // Close enough horizontally, start final descent + currentState = RepairState.LANDING; + info("Beginning final landing approach..."); + } + } + + private void handleLanding() { + if (targetLandingSpot == null) { + currentState = RepairState.FINDING_LANDING; + return; + } + + Vec3d playerPos = mc.player.getPos(); + double groundDistance = playerPos.y - (targetLandingSpot.getY() + 1); + + if (mc.player.isOnGround()) { + // Successfully landed + info("Successfully landed at " + mc.player.getBlockPos().toShortString()); + currentState = RepairState.REPAIRING; + repairStartTime = System.currentTimeMillis(); + currentRepairSlot = 0; + + // Enable AutoExp module + // AutoExp autoExp = Modules.get().get(AutoExp.class); + // if (autoExp != null && !autoExp.isActive()) { + // autoExp.toggle(); + // info("Enabled AutoExp for repair operations"); + // } + } else if (groundDistance > 10) { + // Continue descending + Vec3d landingPos = new Vec3d(targetLandingSpot.getX(), targetLandingSpot.getY() + 2, targetLandingSpot.getZ()); + flyToPosition(landingPos); + + // Stop gliding when close to ground for safer landing + if (groundDistance < 5 && mc.player.isGliding()) { + mc.player.stopGliding(); + } + } else { + // Very close to ground, let gravity handle it + if (mc.player.isGliding()) { + mc.player.stopGliding(); + } + } + } + + private void handleRepairing() { + // Check for timeout + if (System.currentTimeMillis() - repairStartTime > repairTimeout.get() * 1000L) { + warning("Repair timeout reached. Resuming flight with current elytra."); + currentState = RepairState.RESUMING; + return; + } + + if (currentRepairSlot >= elytraSlots.size()) { + // Finished cycling through all elytras + info("Completed repair cycle. Selecting best elytra and resuming flight."); + selectBestElytra(); + currentState = RepairState.RESUMING; + return; + } + + // Get current elytra in repair slot + int slotIndex = elytraSlots.get(currentRepairSlot); + ItemStack elytra = mc.player.getInventory().getStack(slotIndex); + + if (elytra.getItem() != Items.ELYTRA) { + // Elytra was moved or consumed, skip this slot + currentRepairSlot++; + return; + } + + // Check if this elytra is fully repaired or good enough + if (!needsRepair(elytra)) { + info("Elytra in slot " + slotIndex + " is sufficiently repaired"); + currentRepairSlot++; + + // Wait a bit before moving to next elytra + timer = 20; // Wait 1 second (20 ticks) + currentState = RepairState.WAITING; + nextState = RepairState.REPAIRING; + } else { + // Find and use experience bottles + int bottleSlot = -1; + for (int i = 0; i < 9; i++) { + ItemStack stack = mc.player.getInventory().getStack(i); + if (stack.getItem() == Items.EXPERIENCE_BOTTLE) { + bottleSlot = i; + break; + } + } + + if (bottleSlot != -1) { + selectHotbarSlot(bottleSlot); + mc.player.setPitch(90); + mc.options.useKey.setPressed(true); + timer = 5; // Wait 0.25 seconds + currentState = RepairState.WAITING; + nextState = RepairState.REPAIRING; + } else { + warning("No experience bottles found. Cannot repair elytra."); + currentState = RepairState.RESUMING; + } + } + } + + private void handleResuming() { + // Disable AutoExp + // AutoExp autoExp = Modules.get().get(AutoExp.class); + // if (autoExp != null && autoExp.isActive()) { + // autoExp.toggle(); + // info("Disabled AutoExp after repair completion"); + // } + + // Ensure best elytra is equipped + selectBestElytra(); + + // Take off and resume flight + switch (resumingStep) { + case 0: + // Jump to start takeoff + mc.options.jumpKey.setPressed(true); + timer = 10; // Wait 0.5 seconds + currentState = RepairState.WAITING; + nextState = RepairState.RESUMING; + resumingStep++; + break; + case 1: + mc.options.jumpKey.setPressed(false); + timer = 20; // Wait 1 second + currentState = RepairState.WAITING; + nextState = RepairState.RESUMING; + resumingStep++; + break; + case 2: + if (mc.player.getEquippedStack(EquipmentSlot.CHEST).getItem() == Items.ELYTRA) { + mc.player.startGliding(); + info("Restarted elytra gliding"); + } + timer = 40; // Wait 2 seconds + currentState = RepairState.WAITING; + nextState = RepairState.RESUMING; + resumingStep++; + break; + case 3: + // Re-enable ElytraFly module + ElytraFly elytraFly = Modules.get().get(ElytraFly.class); + if (elytraFly != null && !elytraFly.isActive()) { + elytraFly.toggle(); + info("Re-enabled ElytraFly module"); + } + resumingStep = 0; + currentState = RepairState.MONITORING; + break; + } + + // Resume stash hunter if it was active + if (wasStashHunterActive && !ElytraController.isActive()) { + // ElytraController will resume automatically when conditions are right + info("Repair sequence complete. Stash hunter will resume automatically."); + } + + if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { + DiscordEmbed embed = new DiscordEmbed( + "Elytra Repair Complete", + "Successfully repaired elytras and resumed flight.\n" + + "Position: " + mc.player.getBlockPos().toShortString() + "\n" + + "Status: Resuming stash hunting operations", + 0x00FF00 + ); + DiscordWebhook.sendMessage("", embed); + } + + resetRepairState(); + currentState = RepairState.MONITORING; + } + + private void handleEmergencyDisconnect() { + error("Emergency disconnect initiated. Saving state and disconnecting..."); + + // Save current trip state + if (ElytraController.isActive()) { + ElytraController.pause(); + } + + if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { + DiscordEmbed embed = new DiscordEmbed( + "Emergency Disconnect", + "Critical elytra repair failure. Bot is disconnecting for safety.\n" + + "Position: " + (mc.player != null ? mc.player.getBlockPos().toShortString() : "Unknown") + "\n" + + "Reason: Unable to repair elytras safely", + 0xFF0000 + ); + DiscordWebhook.sendMessage("@everyone", embed); + } + + // Disconnect from server + if (mc.getNetworkHandler() != null) { + mc.getNetworkHandler().getConnection().disconnect( + net.minecraft.text.Text.of("Emergency disconnect: Elytra repair failed") + ); + } + + this.toggle(); // Disable the module + } + + private void initiateRepairSequence() { + wasStashHunterActive = ElytraController.isActive(); + + // Pause stash hunter + if (ElytraController.isActive()) { + ElytraController.pause(); + info("Paused stash hunter for elytra repair"); + } + + currentState = RepairState.FINDING_LANDING; + landingAttempts = 0; + } + + private void initiateEmergencyDisconnect(String reason) { + error("Initiating emergency disconnect: " + reason); + currentState = RepairState.EMERGENCY_DISCONNECT; + } + + private boolean needsRepair(ItemStack elytra) { + if (elytra.getItem() != Items.ELYTRA) return false; + int remainingDurability = elytra.getMaxDamage() - elytra.getDamage(); + return remainingDurability <= repairThreshold.get(); + } + + private void findElytraSlots() { + elytraSlots.clear(); + + for (int i = 0; i < mc.player.getInventory().size(); i++) { + ItemStack stack = mc.player.getInventory().getStack(i); + if (stack.getItem() == Items.ELYTRA) { + elytraSlots.add(i); + } + } + + info("Found " + elytraSlots.size() + " elytras in inventory"); + } + + private boolean hasRepairableElytras() { + for (int slot : elytraSlots) { + ItemStack elytra = mc.player.getInventory().getStack(slot); + if (elytra.getItem() == Items.ELYTRA) { + int remainingDurability = elytra.getMaxDamage() - elytra.getDamage(); + // Can be repaired if it has at least some durability and can benefit from mending + if (remainingDurability > 10 && elytra.getDamage() > 0) { + return true; + } + } + } + return false; + } + + private boolean isElytraInOffhand(ItemStack targetElytra) { + ItemStack offhand = mc.player.getInventory().getStack(40); // Offhand slot + return offhand.getItem() == Items.ELYTRA && offhand.getDamage() == targetElytra.getDamage(); + } + + private void moveElytraToOffhand(int fromSlot) { + // Click on the elytra in inventory + mc.interactionManager.clickSlot( + mc.player.currentScreenHandler.syncId, + fromSlot < 9 ? fromSlot + 36 : fromSlot, // Convert to screen handler slot + 0, + SlotActionType.PICKUP, + mc.player + ); + + // Click on offhand slot + mc.interactionManager.clickSlot( + mc.player.currentScreenHandler.syncId, + 45, // Offhand slot in screen handler + 0, + SlotActionType.PICKUP, + mc.player + ); + } + + private void selectBestElytra() { + ItemStack bestElytra = null; + int bestSlot = -1; + int bestDurability = 0; + + // Find elytra with highest durability + for (int slot : elytraSlots) { + ItemStack elytra = mc.player.getInventory().getStack(slot); + if (elytra.getItem() == Items.ELYTRA) { + int durability = elytra.getMaxDamage() - elytra.getDamage(); + if (durability > bestDurability) { + bestDurability = durability; + bestElytra = elytra; + bestSlot = slot; + } + } + } + + if (bestElytra != null && bestSlot != -1) { + // Equip the best elytra + equipElytra(bestSlot); + info("Equipped elytra with " + bestDurability + " durability"); + } + } + + private void equipElytra(int slot) { + // Move elytra to chest slot + mc.interactionManager.clickSlot( + mc.player.currentScreenHandler.syncId, + slot < 9 ? slot + 36 : slot, + 0, + SlotActionType.PICKUP, + mc.player + ); + + mc.interactionManager.clickSlot( + mc.player.currentScreenHandler.syncId, + 6, // Chest slot in screen handler + 0, + SlotActionType.PICKUP, + mc.player + ); + } + + private void flyToPosition(Vec3d target) { + if (mc.player == null) return; + + Vec3d playerPos = mc.player.getPos(); + double dx = target.x - playerPos.x; + double dz = target.z - playerPos.z; + double dy = target.y - playerPos.y; + + double horizontalDistance = Math.sqrt(dx * dx + dz * dz); + + if (horizontalDistance > 1.0) { + double yaw = Math.atan2(dz, dx) * 180.0 / Math.PI - 90.0; + double pitch = Math.atan2(dy, horizontalDistance) * 180.0 / Math.PI; + + // Limit pitch for safe flying + pitch = Math.max(-30.0, Math.min(30.0, pitch)); + + mc.player.setYaw((float) yaw); + mc.player.setPitch((float) pitch); + } + } + + private void resetRepairState() { + targetLandingSpot = null; + resumePosition = null; + repairStartTime = 0; + currentRepairSlot = 0; + elytraSlots.clear(); + wasStashHunterActive = false; + landingAttempts = 0; + } + + private void resumeNormalOperation() { + resetRepairState(); + currentState = RepairState.MONITORING; + + // Re-enable ElytraFly if needed + ElytraFly elytraFly = Modules.get().get(ElytraFly.class); + if (elytraFly != null && !elytraFly.isActive() && wasStashHunterActive) { + elytraFly.toggle(); + } + } + + public RepairState getCurrentState() { + return currentState; + } + + public boolean isRepairing() { + return currentState != RepairState.MONITORING; + } + + public String getCurrentStateName() { + return currentState.name(); + } + + private void selectHotbarSlot(int slot) { + if (slot < 0 || slot > 8 || mc.player == null) return; + + if (mc.getNetworkHandler() != null) { + mc.getNetworkHandler().sendPacket(new UpdateSelectedSlotC2SPacket(slot)); + } + ((PlayerInventoryAccessor) mc.player.getInventory()).setSelectedSlot(slot); + } +} diff --git a/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java b/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java index 5707298..2adf793 100644 --- a/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java +++ b/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java @@ -8,7 +8,9 @@ import com.stashhunter.stashhunter.utils.DiscordWebhook; import com.stashhunter.stashhunter.utils.ElytraController; import com.stashhunter.stashhunter.utils.WorldScanner; +import com.stashhunter.stashhunter.modules.AutoElytraRepair; import meteordevelopment.meteorclient.events.world.TickEvent; +import meteordevelopment.meteorclient.systems.modules.Modules; import meteordevelopment.meteorclient.settings.*; import meteordevelopment.meteorclient.systems.modules.Module; import meteordevelopment.meteorclient.systems.waypoints.Waypoint; @@ -226,6 +228,7 @@ public class StashHunterModule extends Module { private int elytraBrokenTicks = 0; private int reEngagingElytraTicks = 0; private boolean wasElytraBroken = false; + private final AutoElytraRepair autoElytraRepair = Modules.get().get(AutoElytraRepair.class); public StashHunterModule() { super(StashHunter.CATEGORY, "stash-hunter", "Automatically finds stashes by flying around and scanning for valuable blocks."); @@ -273,43 +276,54 @@ private void onTick(TickEvent.Post event) { return; } - // Elytra check - if (ElytraController.isActive()) { - ItemStack chestSlot = mc.player.getEquippedStack(EquipmentSlot.CHEST); - boolean elytraMissing = chestSlot.isEmpty() || (chestSlot.getItem() == Items.ELYTRA && chestSlot.isDamaged() && chestSlot.getDamage() >= chestSlot.getMaxDamage() - 1); - - if (elytraMissing) { - wasElytraBroken = true; - elytraBrokenTicks++; - if (elytraBrokenTicks > 40) { // ~2 seconds leeway - // Send Discord notification - DiscordEmbed embed = new DiscordEmbed( - "Out of Elytras!", - "The bot has run out of elytras or the equipped elytra broke, and will now disconnect.", - 0xFF0000 - ); - DiscordWebhook.sendMessage("@everyone", embed); - - // Disconnect from server - if (mc.getNetworkHandler() != null) { - mc.getNetworkHandler().getConnection().disconnect(Text.of("Ran out of elytras or elytra broke.")); - } +// Enhanced Elytra check with repair integration +if (ElytraController.isActive()) { + ItemStack chestSlot = mc.player.getEquippedStack(EquipmentSlot.CHEST); + boolean elytraMissing = chestSlot.isEmpty() || + (chestSlot.getItem() == Items.ELYTRA && chestSlot.isDamaged() && chestSlot.getDamage() >= chestSlot.getMaxDamage() - 1); - // Stop elytra controller - ElytraController.stop(); + // Check if auto repair is handling the situation + if (autoElytraRepair != null && autoElytraRepair.isActive() && autoElytraRepair.isRepairing()) { + // Auto repair is active, let it handle elytra management + return; // Skip normal elytra checks + } - // Deactivate the module - toggle(); - return; // Stop further processing in onTick - } - } else { - elytraBrokenTicks = 0; - if (wasElytraBroken) { - wasElytraBroken = false; - reEngagingElytraTicks = 100; // 5 seconds - } + if (elytraMissing) { + wasElytraBroken = true; + elytraBrokenTicks++; + + // Give auto repair system time to activate before panicking + int timeoutTicks = autoElytraRepair != null && autoElytraRepair.isActive() ? 200 : 40; + + if (elytraBrokenTicks > timeoutTicks) { + // Send Discord notification + DiscordEmbed embed = new DiscordEmbed( + "Out of Elytras!", + "The bot has run out of elytras or all elytras are broken beyond repair, and will now disconnect.", + 0xFF0000 + ); + DiscordWebhook.sendMessage("@everyone", embed); + + // Disconnect from server + if (mc.getNetworkHandler() != null) { + mc.getNetworkHandler().getConnection().disconnect(Text.of("Ran out of elytras or all elytras broken.")); } + + // Stop elytra controller + ElytraController.stop(); + + // Deactivate the module + toggle(); + return; + } + } else { + elytraBrokenTicks = 0; + if (wasElytraBroken) { + wasElytraBroken = false; + reEngagingElytraTicks = 100; // 5 seconds } + } +} // Handle re-engagement if (reEngagingElytraTicks > 0) { diff --git a/src/main/java/com/stashhunter/stashhunter/utils/Config.java b/src/main/java/com/stashhunter/stashhunter/utils/Config.java index e481838..5702bc6 100644 --- a/src/main/java/com/stashhunter/stashhunter/utils/Config.java +++ b/src/main/java/com/stashhunter/stashhunter/utils/Config.java @@ -39,6 +39,7 @@ public class Config { public static int stuckDetectorThreshold = 3; public static boolean stuckDetectorAutoFix = true; + // Storage containers only (for stash finding) public static List storageBlocks = new ArrayList<>(Arrays.asList( Blocks.CHEST, diff --git a/src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java b/src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java new file mode 100644 index 0000000..d01a0ed --- /dev/null +++ b/src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java @@ -0,0 +1,345 @@ +package com.stashhunter.stashhunter.utils; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.HashSet; +import java.util.Set; + +public class SafeLandingSpotFinder { + + // Blocks that are safe to land on + private static final Set SAFE_LANDING_BLOCKS = Set.of( + Blocks.GRASS_BLOCK, + Blocks.DIRT, + Blocks.STONE, + Blocks.COBBLESTONE, + Blocks.SAND, + Blocks.GRAVEL, + Blocks.NETHERRACK, + Blocks.END_STONE, + Blocks.OBSIDIAN, + Blocks.DEEPSLATE, + Blocks.ANDESITE, + Blocks.DIORITE, + Blocks.GRANITE, + Blocks.SANDSTONE, + Blocks.RED_SANDSTONE + ); + + // Blocks that are hazardous to land on or near + private static final Set HAZARDOUS_BLOCKS = Set.of( + Blocks.LAVA, + Blocks.WATER, + Blocks.CACTUS, + Blocks.SWEET_BERRY_BUSH, + Blocks.WITHER_ROSE, + Blocks.FIRE, + Blocks.SOUL_FIRE, + Blocks.MAGMA_BLOCK, + Blocks.CAMPFIRE, + Blocks.SOUL_CAMPFIRE + ); + + // Blocks that would cause head bumping when taking off + private static final Set OVERHEAD_HAZARDS = Set.of( + Blocks.STONE, + Blocks.COBBLESTONE, + Blocks.DEEPSLATE, + Blocks.ANDESITE, + Blocks.DIORITE, + Blocks.GRANITE, + Blocks.OBSIDIAN, + Blocks.BEDROCK, + Blocks.NETHERRACK, + Blocks.END_STONE, + Blocks.SANDSTONE, + Blocks.RED_SANDSTONE, + Blocks.DIRT, + Blocks.GRASS_BLOCK + ); + + /** + * Finds a safe landing spot within the specified radius + * @param startPos Current player position + * @param radius Search radius in blocks + * @param world The world to search in + * @return A safe landing position, or null if none found + */ + + public static BlockPos findLandingSpot(Vec3d startPos, int radius, ClientWorld world, boolean findBest) { + if (world == null) return null; + + BlockPos bestSpot = null; + int bestScore = -1; + + BlockPos centerPos = BlockPos.ofFloored(startPos); + + // Search in expanding rings for performance + for (int r = 8; r <= radius; r += 8) { + for (int x = -r; x <= r; x += 4) { + for (int z = -r; z <= r; z += 4) { + // Only check positions roughly on the circle edge for this radius + double distance = Math.sqrt(x * x + z * z); + if (distance < r - 4 || distance > r + 4) continue; + + BlockPos checkPos = centerPos.add(x, 0, z); + BlockPos candidate = findSafeLandingAtColumn(checkPos, world); + + if (candidate != null) { + if (!findBest) { + return candidate; + } + + int score = evaluateLandingSpotSafety(candidate, world); + if (score > bestScore) { + bestScore = score; + bestSpot = candidate; + } + + // If we found a really good spot, use it + if (score >= 80) { + return candidate; + } + } + } + } + + // If we found any decent spot in this ring, don't search further + if (findBest && bestScore >= 60) { + break; + } + } + + return bestSpot; + } + + + /** + * Finds a safe landing spot in a vertical column + * @param columnPos The X,Z position to check vertically + * @param world The world to search in + * @return A safe landing position in this column, or null if none found + */ + private static BlockPos findSafeLandingAtColumn(BlockPos columnPos, ClientWorld world) { + // Start from a reasonable height and work down + int maxY = Math.min(world.getHeight() - 1, columnPos.getY() + 50); + int minY = Math.max(world.getBottomY(), columnPos.getY() - 100); + + for (int y = maxY; y >= minY; y--) { + BlockPos landingPos = new BlockPos(columnPos.getX(), y, columnPos.getZ()); + + if (isSafeLandingSpot(landingPos, world)) { + return landingPos; + } + } + + return null; + } + + /** + * Checks if a specific position is safe for landing + * @param pos The position to check + * @param world The world context + * @return true if safe to land here + */ + public static boolean isSafeLandingSpot(BlockPos pos, ClientWorld world) { + try { + // Check the landing block itself + BlockState landingBlock = world.getBlockState(pos); + if (!SAFE_LANDING_BLOCKS.contains(landingBlock.getBlock()) || !landingBlock.isFullCube(world, pos)) { + return false; + } + + // Check that the two blocks above are air (space for player) + BlockPos abovePos1 = pos.up(); + BlockPos abovePos2 = pos.up(2); + + if (!world.getBlockState(abovePos1).isAir() || !world.getBlockState(abovePos2).isAir()) { + return false; + } + + // Check for overhead hazards that would block takeoff (up to 10 blocks above) + for (int i = 3; i <= 10; i++) { + BlockPos overheadPos = pos.up(i); + BlockState overheadBlock = world.getBlockState(overheadPos); + + if (OVERHEAD_HAZARDS.contains(overheadBlock.getBlock()) && overheadBlock.isFullCube(world, overheadPos)) { + return false; // Would hit head during takeoff + } + } + + // Check surrounding area for hazards (3x3 area) + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + BlockPos surroundingPos = pos.add(dx, 0, dz); + BlockState surroundingBlock = world.getBlockState(surroundingPos); + + if (HAZARDOUS_BLOCKS.contains(surroundingBlock.getBlock())) { + return false; + } + + // Also check one block above surrounding area for hanging hazards + BlockPos aboveSurrounding = surroundingPos.up(); + BlockState aboveSurroundingBlock = world.getBlockState(aboveSurrounding); + + if (HAZARDOUS_BLOCKS.contains(aboveSurroundingBlock.getBlock())) { + return false; + } + } + } + + // Check that we're not landing in a confined space (check 5x5 area for walls) + int wallCount = 0; + for (int dx = -2; dx <= 2; dx++) { + for (int dz = -2; dz <= 2; dz++) { + if (dx == 0 && dz == 0) continue; // Skip center position + + BlockPos wallCheckPos = pos.add(dx, 1, dz); // Check at player height + BlockState wallBlock = world.getBlockState(wallCheckPos); + + if (!wallBlock.isAir() && wallBlock.isFullCube(world, wallCheckPos)) { + wallCount++; + } + } + } + + // If more than 60% of surrounding area is walled, it's too confined + if (wallCount > 15) { // 60% of 24 surrounding blocks + return false; + } + + // Additional safety checks for specific biomes/situations + + // Check if we're above void (in End or below bedrock) + if (pos.getY() < world.getBottomY() + 5) { + return false; + } + + // Check if landing spot has solid ground beneath (not floating) + boolean hasGroundSupport = false; + for (int i = 1; i <= 5; i++) { + BlockPos belowPos = pos.down(i); + BlockState belowBlock = world.getBlockState(belowPos); + + if (!belowBlock.isAir() && belowBlock.isFullCube(world, belowPos)) { + hasGroundSupport = true; + break; + } + } + + if (!hasGroundSupport) { + return false; // Floating platform, not safe + } + + return true; // All checks passed + + } catch (Exception e) { + // If we can't check the blocks safely, assume it's not safe + return false; + } + } + + /** + * Evaluates the safety score of a landing spot (higher is better) + * @param pos The position to evaluate + * @param world The world context + * @return Safety score from 0-100, or -1 if not safe at all + */ + public static int evaluateLandingSpotSafety(BlockPos pos, ClientWorld world) { + if (!isSafeLandingSpot(pos, world)) { + return -1; + } + + int safetyScore = 50; // Base score for any safe spot + + try { + // Bonus points for being on natural ground blocks + BlockState landingBlock = world.getBlockState(pos); + if (landingBlock.getBlock() == Blocks.GRASS_BLOCK || + landingBlock.getBlock() == Blocks.STONE || + landingBlock.getBlock() == Blocks.DIRT) { + safetyScore += 10; + } + + // Bonus points for having more open space above + int openSpaceAbove = 0; + for (int i = 3; i <= 15; i++) { + BlockPos abovePos = pos.up(i); + if (world.getBlockState(abovePos).isAir()) { + openSpaceAbove++; + } else { + break; + } + } + safetyScore += Math.min(openSpaceAbove * 2, 20); // Up to 20 bonus points + + // Bonus points for having more open space around + int openSpaceAround = 0; + for (int dx = -3; dx <= 3; dx++) { + for (int dz = -3; dz <= 3; dz++) { + if (dx == 0 && dz == 0) continue; + + BlockPos aroundPos = pos.add(dx, 1, dz); + if (world.getBlockState(aroundPos).isAir()) { + openSpaceAround++; + } + } + } + safetyScore += Math.min(openSpaceAround, 15); // Up to 15 bonus points + + // Penalty for being too high above + int distanceToGround = 0; + for (int i = 1; i <= 50; i++) { + BlockPos belowPos = pos.down(i); + BlockState belowBlock = world.getBlockState(belowPos); + + if (!belowBlock.isAir()) { + distanceToGround = i - 1; + break; + } + } + + if (distanceToGround > 10) { + safetyScore -= (distanceToGround - 10) * 2; // Penalty for being too high + } + + return Math.max(0, Math.min(100, safetyScore)); + + } catch (Exception e) { + return 50; // Return base score if evaluation fails + } + } + + + /** + * Quick check if the immediate area below is safe for emergency landing + * @param pos Current position + * @param world The world context + * @return true if it's safe to descend here immediately + */ + public static boolean isEmergencyLandingSafe(Vec3d pos, ClientWorld world) { + BlockPos checkPos = BlockPos.ofFloored(pos); + + // Look for ground within reasonable distance below + for (int i = 0; i < 50; i++) { + BlockPos groundPos = checkPos.down(i); + if (isSafeLandingSpot(groundPos, world)) { + return true; + } + + // If we hit a hazardous block, stop checking + BlockState blockState = world.getBlockState(groundPos); + if (HAZARDOUS_BLOCKS.contains(blockState.getBlock())) { + return false; + } + } + + return false; + } +} diff --git a/src/main/resources/addon-template.mixins.json b/src/main/resources/addon-template.mixins.json index da0abd4..d783752 100644 --- a/src/main/resources/addon-template.mixins.json +++ b/src/main/resources/addon-template.mixins.json @@ -3,7 +3,8 @@ "package": "com.stashhunter.stashhunter.mixin", "compatibilityLevel": "JAVA_21", "client": [ - "LivingEntityMixin" + "LivingEntityMixin", + "PlayerInventoryAccessor" ], "injectors": { "defaultRequire": 1 From 67e7e1fc2788282e3f24aed1f73d174411dc5365 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:44:26 +0000 Subject: [PATCH 03/12] Fix: Prevent emergency disconnect in Auto Elytra Repair Removes the emergency disconnect functionality from the Auto Elytra Repair module. The module will no longer disconnect the player if it fails to find a safe landing spot after a set number of attempts. Instead, it will now continuously expand its search radius and keep trying to find a landing spot indefinitely. This change also fixes a bug where the expanding search radius was calculated but not correctly used in the landing spot search function. --- .../stashhunter/modules/AutoElytraRepair.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java b/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java index 924bad9..e5ef2bd 100644 --- a/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java +++ b/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java @@ -204,9 +204,12 @@ private void handleMonitoring() { private void handleFindingLanding() { Vec3d playerPos = mc.player.getPos(); + // Calculate search radius based on attempts + int currentRadius = landingRadius.get() * (landingAttempts + 1); + // Find safe landing spot BlockPos landingSpot = SafeLandingSpotFinder.findLandingSpot( - playerPos, landingRadius.get(), mc.world, false + playerPos, currentRadius, mc.world, false ); if (landingSpot != null) { @@ -214,18 +217,10 @@ private void handleFindingLanding() { resumePosition = playerPos; // Remember where we were currentState = RepairState.DESCENDING; info("Found safe landing spot at " + landingSpot.toShortString()); + landingAttempts = 0; // Reset for next time } else { landingAttempts++; - if (landingAttempts >= MAX_LANDING_ATTEMPTS) { - error("Could not find safe landing spot after " + MAX_LANDING_ATTEMPTS + " attempts!"); - initiateEmergencyDisconnect("No safe landing spot found"); - return; - } - - // Try expanding search radius - int expandedRadius = landingRadius.get() * (landingAttempts + 1); - info("Expanding landing search radius to " + expandedRadius + " blocks..."); - + info("Could not find landing spot, expanding search radius to " + (landingRadius.get() * (landingAttempts + 1)) + " blocks..."); // Continue searching with expanded radius next tick } } From f7c40513d6989dc51bfe8a706a328ca1a7b0c8d6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 04:03:22 +0000 Subject: [PATCH 04/12] Fix a crash caused by a Mixin accessor failing to find its target field. The `PlayerInventoryAccessor` mixin was using an explicit field name in the `@Accessor` annotation, which caused a runtime error after a Minecraft update. By removing the explicit field name, the Mixin framework can now correctly infer the target field from the accessor method's name, making the code more robust and resilient to future updates. --- .../stashhunter/stashhunter/mixin/PlayerInventoryAccessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/stashhunter/stashhunter/mixin/PlayerInventoryAccessor.java b/src/main/java/com/stashhunter/stashhunter/mixin/PlayerInventoryAccessor.java index ab65b12..1747322 100644 --- a/src/main/java/com/stashhunter/stashhunter/mixin/PlayerInventoryAccessor.java +++ b/src/main/java/com/stashhunter/stashhunter/mixin/PlayerInventoryAccessor.java @@ -6,6 +6,6 @@ @Mixin(PlayerInventory.class) public interface PlayerInventoryAccessor { - @Accessor("selectedSlot") + @Accessor void setSelectedSlot(int slot); } From 3511f99dfdff4f55b711efca703bdbef25d45339 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 04:43:12 +0000 Subject: [PATCH 05/12] Refactor auto-repair and resume logic for improved reliability. This commit overhauls the auto-repair and flight resume systems to be more robust and efficient. Key changes: - The elytra repair logic now continuously throws XP bottles until the elytra is fully repaired or the player runs out of bottles, replacing the old inefficient timer-based system. - The takeoff sequence after a repair is now managed by a state machine that checks the player's vertical velocity, making it more reliable than the previous timer-based implementation. - A new "climbing" state has been added to the flight controller. After a repair, the bot will now climb back to the target flight altitude before resuming its path. - The default flight altitude has been changed to 160 blocks as requested. - A compilation error related to accessing a private field in `PlayerInventory` has been fixed. --- .../stashhunter/modules/AutoElytraRepair.java | 187 ++++++++++-------- .../modules/StashHunterModule.java | 4 + .../stashhunter/stashhunter/utils/Config.java | 4 +- .../stashhunter/utils/ElytraController.java | 24 ++- 4 files changed, 129 insertions(+), 90 deletions(-) diff --git a/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java b/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java index e5ef2bd..f6b1698 100644 --- a/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java +++ b/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java @@ -78,6 +78,7 @@ public class AutoElytraRepair extends Module { private int timer = 0; private int resumingStep = 0; private RepairState nextState = null; + private boolean justFinishedRepairing = false; private enum RepairState { MONITORING, // Normal operation, checking elytra durability @@ -85,6 +86,7 @@ private enum RepairState { DESCENDING, // Flying to landing spot LANDING, // Final landing approach REPAIRING, // On ground, cycling through elytras for repair + REPAIRING_IN_PROGRESS, // Actively using XP bottles RESUMING, // Taking off and resuming flight EMERGENCY_DISCONNECT, // Critical failure, preparing to disconnect WAITING @@ -130,6 +132,9 @@ private void onTick(TickEvent.Post event) { case REPAIRING: handleRepairing(); break; + case REPAIRING_IN_PROGRESS: + handleRepairingInProgress(); + break; case RESUMING: handleResuming(); break; @@ -313,17 +318,14 @@ private void handleRepairing() { return; } - // Check if this elytra is fully repaired or good enough - if (!needsRepair(elytra)) { - info("Elytra in slot " + slotIndex + " is sufficiently repaired"); + // Check if this elytra is already fully repaired + if (isFullyRepaired(elytra)) { + info("Elytra in slot " + slotIndex + " is fully repaired."); currentRepairSlot++; - - // Wait a bit before moving to next elytra - timer = 20; // Wait 1 second (20 ticks) - currentState = RepairState.WAITING; - nextState = RepairState.REPAIRING; + // Move to the next elytra immediately + currentState = RepairState.REPAIRING; } else { - // Find and use experience bottles + // Find experience bottles in the hotbar int bottleSlot = -1; for (int i = 0; i < 9; i++) { ItemStack stack = mc.player.getInventory().getStack(i); @@ -334,88 +336,112 @@ private void handleRepairing() { } if (bottleSlot != -1) { + // Found bottles, start the repair process selectHotbarSlot(bottleSlot); mc.player.setPitch(90); - mc.options.useKey.setPressed(true); - timer = 5; // Wait 0.25 seconds - currentState = RepairState.WAITING; - nextState = RepairState.REPAIRING; + currentState = RepairState.REPAIRING_IN_PROGRESS; + mc.options.useKey.setPressed(true); // Start throwing + info("Starting repair for elytra in slot " + slotIndex); } else { - warning("No experience bottles found. Cannot repair elytra."); - currentState = RepairState.RESUMING; + warning("No experience bottles found. Cannot continue repair."); + currentState = RepairState.RESUMING; // No bottles left, give up } } } - private void handleResuming() { - // Disable AutoExp - // AutoExp autoExp = Modules.get().get(AutoExp.class); - // if (autoExp != null && autoExp.isActive()) { - // autoExp.toggle(); - // info("Disabled AutoExp after repair completion"); - // } + private void handleRepairingInProgress() { + // Continue holding the use key + mc.options.useKey.setPressed(true); + + // Get current elytra being repaired + int slotIndex = elytraSlots.get(currentRepairSlot); + ItemStack elytra = mc.player.getInventory().getStack(slotIndex); - // Ensure best elytra is equipped - selectBestElytra(); + // Check if we ran out of bottles in the selected slot + if (mc.player.getInventory().getStack(mc.player.getInventory().getSelectedSlot()).getItem() != Items.EXPERIENCE_BOTTLE) { + mc.options.useKey.setPressed(false); // Stop throwing + warning("Ran out of experience bottles in hotbar slot."); + currentState = RepairState.REPAIRING; // Go back to find more bottles or move to next elytra + return; + } + + // Check if the elytra is now fully repaired + if (isFullyRepaired(elytra)) { + mc.options.useKey.setPressed(false); // Stop throwing + info("Finished repairing elytra in slot " + slotIndex); + currentRepairSlot++; + currentState = RepairState.REPAIRING; // Move to the next elytra + } - // Take off and resume flight + // Also check for timeout here as a safety measure + if (System.currentTimeMillis() - repairStartTime > repairTimeout.get() * 1000L) { + mc.options.useKey.setPressed(false); // Stop throwing + warning("Repair timeout reached during repair-in-progress."); + currentState = RepairState.RESUMING; + } + } + + private void handleResuming() { + // This method now uses a state machine to make takeoff more reliable switch (resumingStep) { case 0: - // Jump to start takeoff - mc.options.jumpKey.setPressed(true); - timer = 10; // Wait 0.5 seconds - currentState = RepairState.WAITING; - nextState = RepairState.RESUMING; + // Ensure best elytra is equipped + selectBestElytra(); + info("Beginning takeoff sequence..."); resumingStep++; break; case 1: - mc.options.jumpKey.setPressed(false); - timer = 20; // Wait 1 second - currentState = RepairState.WAITING; - nextState = RepairState.RESUMING; - resumingStep++; + // Jump to gain height + mc.options.jumpKey.setPressed(true); + // Wait until we are in the air + if (!mc.player.isOnGround()) { + mc.options.jumpKey.setPressed(false); + resumingStep++; + } break; case 2: - if (mc.player.getEquippedStack(EquipmentSlot.CHEST).getItem() == Items.ELYTRA) { - mc.player.startGliding(); - info("Restarted elytra gliding"); + // Wait until we start falling, then activate elytra + if (mc.player.getVelocity().y < -0.1) { + if (mc.player.getEquippedStack(EquipmentSlot.CHEST).getItem() == Items.ELYTRA) { + mc.player.startGliding(); + info("Elytra gliding activated."); + resumingStep++; + } else { + // Something went wrong, abort + error("No elytra equipped during takeoff, aborting resume."); + currentState = RepairState.MONITORING; + resumingStep = 0; + } } - timer = 40; // Wait 2 seconds - currentState = RepairState.WAITING; - nextState = RepairState.RESUMING; - resumingStep++; break; case 3: - // Re-enable ElytraFly module + // Re-enable ElytraFly and finish ElytraFly elytraFly = Modules.get().get(ElytraFly.class); if (elytraFly != null && !elytraFly.isActive()) { elytraFly.toggle(); - info("Re-enabled ElytraFly module"); + info("Re-enabled ElytraFly module."); + } + + // Notify completion + if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { + DiscordEmbed embed = new DiscordEmbed( + "Elytra Repair Complete", + "Successfully repaired elytras and resumed flight.\n" + + "Position: " + mc.player.getBlockPos().toShortString() + "\n" + + "Status: Resuming stash hunting operations", + 0x00FF00 + ); + DiscordWebhook.sendMessage("", embed); } + + // Final cleanup + resetRepairState(); resumingStep = 0; + justFinishedRepairing = true; // Set the flag for StashHunterModule currentState = RepairState.MONITORING; + info("Repair sequence complete. Stash hunter will resume automatically."); break; } - - // Resume stash hunter if it was active - if (wasStashHunterActive && !ElytraController.isActive()) { - // ElytraController will resume automatically when conditions are right - info("Repair sequence complete. Stash hunter will resume automatically."); - } - - if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { - DiscordEmbed embed = new DiscordEmbed( - "Elytra Repair Complete", - "Successfully repaired elytras and resumed flight.\n" + - "Position: " + mc.player.getBlockPos().toShortString() + "\n" + - "Status: Resuming stash hunting operations", - 0x00FF00 - ); - DiscordWebhook.sendMessage("", embed); - } - - resetRepairState(); - currentState = RepairState.MONITORING; } private void handleEmergencyDisconnect() { @@ -498,29 +524,8 @@ private boolean hasRepairableElytras() { return false; } - private boolean isElytraInOffhand(ItemStack targetElytra) { - ItemStack offhand = mc.player.getInventory().getStack(40); // Offhand slot - return offhand.getItem() == Items.ELYTRA && offhand.getDamage() == targetElytra.getDamage(); - } - - private void moveElytraToOffhand(int fromSlot) { - // Click on the elytra in inventory - mc.interactionManager.clickSlot( - mc.player.currentScreenHandler.syncId, - fromSlot < 9 ? fromSlot + 36 : fromSlot, // Convert to screen handler slot - 0, - SlotActionType.PICKUP, - mc.player - ); - - // Click on offhand slot - mc.interactionManager.clickSlot( - mc.player.currentScreenHandler.syncId, - 45, // Offhand slot in screen handler - 0, - SlotActionType.PICKUP, - mc.player - ); + private boolean isFullyRepaired(ItemStack elytra) { + return elytra.getDamage() == 0; } private void selectBestElytra() { @@ -622,6 +627,14 @@ public String getCurrentStateName() { return currentState.name(); } + public boolean justFinishedRepair() { + if (justFinishedRepairing) { + justFinishedRepairing = false; // Reset after checking + return true; + } + return false; + } + private void selectHotbarSlot(int slot) { if (slot < 0 || slot > 8 || mc.player == null) return; diff --git a/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java b/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java index 2adf793..564c5fd 100644 --- a/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java +++ b/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java @@ -272,6 +272,10 @@ private void onTick(TickEvent.Post event) { tickCounter++; ElytraController.onTick(); + if (autoElytraRepair.justFinishedRepair()) { + ElytraController.climbToAltitude(); + } + if (mc.player == null || mc.world == null) { return; } diff --git a/src/main/java/com/stashhunter/stashhunter/utils/Config.java b/src/main/java/com/stashhunter/stashhunter/utils/Config.java index 5702bc6..48c17d7 100644 --- a/src/main/java/com/stashhunter/stashhunter/utils/Config.java +++ b/src/main/java/com/stashhunter/stashhunter/utils/Config.java @@ -21,7 +21,7 @@ public class Config { public static String discordWebhookUrl = ""; public static int blockDetectionThreshold = 10; // Increased from 8 to reduce false positives public static int scanRadius = 64; // Reduced from 128 to avoid natural structures - public static int flightAltitude = 320; + public static int flightAltitude = 160; public static int scanInterval = 40; public static boolean playerDetection = true; public static boolean notifyOnDeath = true; @@ -144,7 +144,7 @@ public static void load() { discordWebhookUrl = properties.getProperty("discordWebhookUrl", ""); blockDetectionThreshold = getIntProperty("blockDetectionThreshold", 10); scanRadius = getIntProperty("scanRadius", 64); - flightAltitude = getIntProperty("flightAltitude", 320); + flightAltitude = getIntProperty("flightAltitude", 160); scanInterval = getIntProperty("scanInterval", 40); playerDetection = getBoolProperty("playerDetection", true); notifyOnDeath = getBoolProperty("notifyOnDeath", true); diff --git a/src/main/java/com/stashhunter/stashhunter/utils/ElytraController.java b/src/main/java/com/stashhunter/stashhunter/utils/ElytraController.java index 69fb7eb..e2a77ed 100644 --- a/src/main/java/com/stashhunter/stashhunter/utils/ElytraController.java +++ b/src/main/java/com/stashhunter/stashhunter/utils/ElytraController.java @@ -44,7 +44,8 @@ public class ElytraController { private enum NavigationMode { NORMAL, // Following waypoints normally EDGE_FOLLOWING, // Following chunk boundary - TRAIL_FOLLOWING // Following new chunk trail + TRAIL_FOLLOWING, // Following new chunk trail + CLIMBING // Climbing to target altitude } public static void start(int x1, int z1, int x2, int z2, int stripWidth) { @@ -198,6 +199,27 @@ public static void onTick() { case TRAIL_FOLLOWING: handleTrailFollowing(); break; + case CLIMBING: + handleClimbing(); + break; + } + } + + public static void climbToAltitude() { + if (MeteorClient.mc.player == null) return; + navigationMode = NavigationMode.CLIMBING; + Vec3d playerPos = MeteorClient.mc.player.getPos(); + currentTarget = new Vec3d(playerPos.x, Config.flightAltitude, playerPos.z); + Logger.log("Climbing to altitude: " + Config.flightAltitude); + } + + private static void handleClimbing() { + if (MeteorClient.mc.player == null) return; + if (MeteorClient.mc.player.getPos().y >= Config.flightAltitude - 2) { + navigationMode = NavigationMode.NORMAL; + Logger.log("Reached target altitude, resuming normal navigation."); + } else { + controlFlight(currentTarget); } } From 7ba815d18d49a8da1fd976a2bd023f5223a8da98 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 04:54:09 +0000 Subject: [PATCH 06/12] Refactor auto-repair and resume logic for improved reliability. This commit overhauls the auto-repair and flight resume systems to be more robust and efficient. Key changes: - The elytra repair logic now continuously throws XP bottles until the elytra is fully repaired or the player runs out of bottles, replacing the old inefficient timer-based system. - The takeoff sequence after a repair is now managed by a state machine that checks the player's vertical velocity, making it more reliable than the previous timer-based implementation. - A new "climbing" state has been added to the flight controller. After a repair, the bot will now climb back to the target flight altitude before resuming its path. - The default flight altitude has been changed to 160 blocks as requested. - A compilation error related to accessing a private field in `PlayerInventory` has been fixed. - Fixed a `NullPointerException` caused by a missing null check when accessing the `autoElytraRepair` module. --- .../com/stashhunter/stashhunter/modules/StashHunterModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java b/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java index 564c5fd..1dab813 100644 --- a/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java +++ b/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java @@ -272,7 +272,7 @@ private void onTick(TickEvent.Post event) { tickCounter++; ElytraController.onTick(); - if (autoElytraRepair.justFinishedRepair()) { + if (autoElytraRepair != null && autoElytraRepair.justFinishedRepair()) { ElytraController.climbToAltitude(); } From 3eeda8b346500a6761d2907ee4c10a069879d0e6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 05:10:13 +0000 Subject: [PATCH 07/12] Refactor auto-repair and fix safe landing logic. This commit overhauls the auto-repair and flight resume systems and fixes a critical bug in the safe landing logic. Key changes: - The safe landing logic in `SafeLandingSpotFinder.java` has been fixed to explicitly check for water, preventing the bot from landing in oceans. - The elytra repair logic now continuously throws XP bottles until the elytra is fully repaired or the player runs out of bottles. - The takeoff sequence after a repair is now managed by a state machine that checks the player's vertical velocity, making it more reliable. - A new "climbing" state has been added to the flight controller to ensure the bot returns to the target flight altitude after repairs. - The default flight altitude has been changed to 160 blocks as requested. - Fixed a `NullPointerException` caused by a missing null check. --- .../stashhunter/utils/SafeLandingSpotFinder.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java b/src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java index d01a0ed..a88459b 100644 --- a/src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java +++ b/src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java @@ -156,11 +156,15 @@ public static boolean isSafeLandingSpot(BlockPos pos, ClientWorld world) { return false; } - // Check that the two blocks above are air (space for player) - BlockPos abovePos1 = pos.up(); - BlockPos abovePos2 = pos.up(2); + // Check for water at player level + BlockPos playerFeetPos = pos.up(); + BlockPos playerHeadPos = pos.up(2); + if (world.getBlockState(playerFeetPos).getBlock() == Blocks.WATER || world.getBlockState(playerHeadPos).getBlock() == Blocks.WATER) { + return false; + } - if (!world.getBlockState(abovePos1).isAir() || !world.getBlockState(abovePos2).isAir()) { + // Check that the two blocks above are air (space for player) + if (!world.getBlockState(playerFeetPos).isAir() || !world.getBlockState(playerHeadPos).isAir()) { return false; } From f2619f3bee1cf708dff994b7ba34b069acc447bf Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 05:49:49 +0000 Subject: [PATCH 08/12] Fix auto-repair and flight resume logic. This commit addresses several issues with the auto-repair system, including fixing a crash, preventing ocean landings, and making the flight resume process more reliable. Key changes: - Fixed a bug that caused the bot to get stuck during descent by ensuring movement is active during the landing sequence. - Added an explicit check for water in `SafeLandingSpotFinder.java` to prevent the bot from landing in oceans. - Refactored the elytra repair logic to continuously use XP bottles until the elytra is fully repaired. - Overhauled the takeoff sequence to be state-based instead of timer-based for better reliability. - Added a "climbing" state to ensure the bot returns to the target flight altitude after repairs. - Set the default flight altitude to 160 as requested. - Fixed an initial `InvalidAccessorException` crash and a subsequent `NullPointerException`. --- .../stashhunter/modules/AutoElytraRepair.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java b/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java index f6b1698..e217642 100644 --- a/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java +++ b/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java @@ -104,6 +104,7 @@ public void onActivate() { @Override public void onDeactivate() { + mc.options.forwardKey.setPressed(false); if (currentState != RepairState.MONITORING) { // Emergency cleanup resumeNormalOperation(); @@ -264,6 +265,7 @@ private void handleLanding() { if (mc.player.isOnGround()) { // Successfully landed + mc.options.forwardKey.setPressed(false); info("Successfully landed at " + mc.player.getBlockPos().toShortString()); currentState = RepairState.REPAIRING; repairStartTime = System.currentTimeMillis(); @@ -445,6 +447,7 @@ private void handleResuming() { } private void handleEmergencyDisconnect() { + mc.options.forwardKey.setPressed(false); error("Emergency disconnect initiated. Saving state and disconnecting..."); // Save current trip state @@ -575,6 +578,11 @@ private void equipElytra(int slot) { private void flyToPosition(Vec3d target) { if (mc.player == null) return; + // Ensure we are gliding + if (!mc.player.isGliding()) { + mc.player.startGliding(); + } + Vec3d playerPos = mc.player.getPos(); double dx = target.x - playerPos.x; double dz = target.z - playerPos.z; @@ -592,6 +600,9 @@ private void flyToPosition(Vec3d target) { mc.player.setYaw((float) yaw); mc.player.setPitch((float) pitch); } + + // Move forward + mc.options.forwardKey.setPressed(true); } private void resetRepairState() { From 9100564e6644b8096ad571a414407f05e946bb12 Mon Sep 17 00:00:00 2001 From: omtoi101 Date: Fri, 26 Sep 2025 01:08:05 +1000 Subject: [PATCH 09/12] fixed lol --- bin/main/addon-template.mixins.json | 5 +- bin/main/fabric.mod.json | 10 +- .../stashhunter/modules/AutoElytraRepair.java | 868 +++++++++--------- .../utils/SafeLandingSpotFinder.java | 349 ------- 4 files changed, 438 insertions(+), 794 deletions(-) delete mode 100644 src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java diff --git a/bin/main/addon-template.mixins.json b/bin/main/addon-template.mixins.json index ffa30f9..d783752 100644 --- a/bin/main/addon-template.mixins.json +++ b/bin/main/addon-template.mixins.json @@ -1,9 +1,10 @@ { "required": true, - "package": "com.baseminer.basefinder.mixin", + "package": "com.stashhunter.stashhunter.mixin", "compatibilityLevel": "JAVA_21", "client": [ - "LivingEntityMixin" + "LivingEntityMixin", + "PlayerInventoryAccessor" ], "injectors": { "defaultRequire": 1 diff --git a/bin/main/fabric.mod.json b/bin/main/fabric.mod.json index d260342..65c9b5b 100644 --- a/bin/main/fabric.mod.json +++ b/bin/main/fabric.mod.json @@ -1,20 +1,20 @@ { "schemaVersion": 1, - "id": "base-finder", + "id": "stash-hunter", "version": "${version}", - "name": "Base Finder", - "description": "A Meteor client addon for finding bases on anarchy servers.", + "name": "Stash-Hunter", + "description": "A Meteor client addon for finding stashes on anarchy servers.", "authors": [ "omtoi" ], "contact": { - "sources": "https://github.com/omtoi101/stash-finder" + "sources": "https://github.com/omtoi101/stash-hunter" }, "icon": "assets/template/icon.png", "environment": "client", "entrypoints": { "meteor": [ - "com.baseminer.basefinder.BaseFinder" + "com.stashhunter.stashhunter.StashHunter" ] }, "mixins": [ diff --git a/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java b/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java index e217642..6245f52 100644 --- a/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java +++ b/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java @@ -1,30 +1,23 @@ package com.stashhunter.stashhunter.modules; import com.stashhunter.stashhunter.StashHunter; -import com.stashhunter.stashhunter.utils.Config; -import com.stashhunter.stashhunter.utils.DiscordEmbed; -import com.stashhunter.stashhunter.utils.DiscordWebhook; +import com.stashhunter.stashhunter.utils.KeyHold; import com.stashhunter.stashhunter.utils.ElytraController; -import com.stashhunter.stashhunter.utils.SafeLandingSpotFinder; 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.misc.AutoTool; // Not found in project -// import meteordevelopment.meteorclient.systems.modules.player.AutoExp; // Not found in project +import meteordevelopment.meteorclient.systems.modules.combat.AutoEXP; +import meteordevelopment.meteorclient.systems.modules.movement.Scaffold; import meteordevelopment.meteorclient.systems.modules.movement.elytrafly.ElytraFly; import meteordevelopment.orbit.EventHandler; -import net.minecraft.block.Block; -import net.minecraft.block.Blocks; import net.minecraft.entity.EquipmentSlot; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; -import net.minecraft.screen.slot.SlotActionType; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; -import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket; -import com.stashhunter.stashhunter.mixin.PlayerInventoryAccessor; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; @@ -40,15 +33,6 @@ public class AutoElytraRepair extends Module { .build() ); - private final Setting landingRadius = sgGeneral.add(new IntSetting.Builder() - .name("landing-radius") - .description("Radius to search for safe landing spots.") - .defaultValue(64) - .min(16) - .sliderMax(128) - .build() - ); - private final Setting notifyRepairs = sgGeneral.add(new BoolSetting.Builder() .name("notify-repairs") .description("Send Discord notifications during repair operations.") @@ -65,31 +49,62 @@ public class AutoElytraRepair extends Module { .build() ); + private final Setting debugMode = sgGeneral.add(new BoolSetting.Builder() + .name("debug-mode") + .description("Enable debug logging for troubleshooting.") + .defaultValue(false) + .build() + ); + // Repair state private RepairState currentState = RepairState.MONITORING; - private BlockPos targetLandingSpot = null; - private Vec3d resumePosition = null; private long repairStartTime = 0; private int currentRepairSlot = 0; private List elytraSlots = new ArrayList<>(); private boolean wasStashHunterActive = false; - private int landingAttempts = 0; - private static final int MAX_LANDING_ATTEMPTS = 3; private int timer = 0; - private int resumingStep = 0; - private RepairState nextState = null; private boolean justFinishedRepairing = false; + // Scaffold-repair specific state + private double descentStartY = 0; + private boolean scaffoldSetupDone = false; + private boolean originalAutoPilotState = false; + private boolean originalAutoHoverState = false; + private boolean originalScaffoldAirPlace = false; + private boolean originalScaffoldAutoSwitch = false; + + // NEW: deceleration wait state (added to allow ElytraFly to slow down before scaffold placement) + private boolean decelWaiting = false; + private boolean decelInitiated = false; // to ensure we only initiate deceleration wait once + private int decelTimer = 0; // ticks to wait for deceleration + + // Movement detection for repair sequence + private int stopWaitTimer = 0; + private Vec3d lastPlayerPosition = null; + private int stationaryTicks = 0; + private static final int REQUIRED_STATIONARY_TICKS = 20; // 1 second of being stationary + + // Climb-to-altitude timeout tracking + private boolean climbingToCruise = false; + private long climbStartTimeMs = 0; + + // Track ElytraFly autoTakeoff original state + private boolean originalAutoTakeoffState = false; + + // Positioning for repair (hover above placed block) + private double targetRepairAltitude = 0; + private long positioningStartMs = 0; + private enum RepairState { - MONITORING, // Normal operation, checking elytra durability - FINDING_LANDING, // Looking for safe landing spot - DESCENDING, // Flying to landing spot - LANDING, // Final landing approach - REPAIRING, // On ground, cycling through elytras for repair - REPAIRING_IN_PROGRESS, // Actively using XP bottles - RESUMING, // Taking off and resuming flight - EMERGENCY_DISCONNECT, // Critical failure, preparing to disconnect - WAITING + MONITORING, + STOPPING_AUTOPILOT, + WAITING_FOR_STOP, + POSITIONING_FOR_REPAIR, + SCAFFOLDING, + REPAIRING, + REPAIRING_IN_PROGRESS, + RESUMING_FLIGHT, + EMERGENCY_DISCONNECT } public AutoElytraRepair() { @@ -100,15 +115,15 @@ public AutoElytraRepair() { public void onActivate() { currentState = RepairState.MONITORING; resetRepairState(); + debugLog("AutoElytraRepair activated"); } @Override public void onDeactivate() { - mc.options.forwardKey.setPressed(false); if (currentState != RepairState.MONITORING) { - // Emergency cleanup resumeNormalOperation(); } + debugLog("AutoElytraRepair deactivated"); } @EventHandler @@ -121,14 +136,17 @@ private void onTick(TickEvent.Post event) { case MONITORING: handleMonitoring(); break; - case FINDING_LANDING: - handleFindingLanding(); + case STOPPING_AUTOPILOT: + handleStoppingAutopilot(); + break; + case WAITING_FOR_STOP: + handleWaitingForStop(); break; - case DESCENDING: - handleDescending(); + case POSITIONING_FOR_REPAIR: + handlePositioningForRepair(); break; - case LANDING: - handleLanding(); + case SCAFFOLDING: + handleScaffolding(); break; case REPAIRING: handleRepairing(); @@ -136,357 +154,360 @@ private void onTick(TickEvent.Post event) { case REPAIRING_IN_PROGRESS: handleRepairingInProgress(); break; - case RESUMING: - handleResuming(); + case RESUMING_FLIGHT: + handleResumingFlight(); break; case EMERGENCY_DISCONNECT: handleEmergencyDisconnect(); break; - case WAITING: - handleWaiting(); - break; } } - private void handleWaiting() { - if (timer > 0) { - timer--; - } else { - if (currentState == RepairState.WAITING && nextState == RepairState.REPAIRING) { - mc.options.useKey.setPressed(false); - } - currentState = nextState; - nextState = null; + private void handleMonitoring() { + if (!ElytraController.isActive()) return; + ItemStack chestSlot = mc.player.getEquippedStack(EquipmentSlot.CHEST); + if (chestSlot.getItem() != Items.ELYTRA) return; + + if (needsRepair(chestSlot)) { + initiateRepairSequence(); } } - private void handleMonitoring() { - if (!ElytraController.isActive()) { - return; // Only monitor when stash hunter is active + private void handleStoppingAutopilot() { + ElytraFly elytraFly = Modules.get().get(ElytraFly.class); + if (elytraFly == null) { + error("ElytraFly not found! Aborting repair."); + currentState = RepairState.EMERGENCY_DISCONNECT; + return; } - ItemStack chestSlot = mc.player.getEquippedStack(EquipmentSlot.CHEST); - if (chestSlot.getItem() != Items.ELYTRA) { - return; // No elytra equipped + // Stop autopilot to allow player to slow down + originalAutoPilotState = getModuleSetting(elytraFly, "autoPilot"); + Boolean takeoffState = getModuleSetting(elytraFly, "autoTakeOff"); + originalAutoTakeoffState = takeoffState != null ? takeoffState : false; + setModuleSetting(elytraFly, "autoPilot", false); + // Immediately release forward to stop autopilot movement without GUI + mc.options.forwardKey.setPressed(false); + // Clear ElytraFly internal forward flag if present + try { + Field modeField = elytraFly.getClass().getDeclaredField("currentMode"); + modeField.setAccessible(true); + Object mode = modeField.get(elytraFly); + if (mode != null) { + try { + Field lastF = mode.getClass().getDeclaredField("lastForwardPressed"); + lastF.setAccessible(true); + lastF.setBoolean(mode, false); + } catch (NoSuchFieldException ignored) {} + } + } catch (Throwable ignored) {} + setModuleSetting(elytraFly, "autoTakeOff", true); + + info("Stopped autopilot. Waiting for player to stop moving..."); + currentState = RepairState.WAITING_FOR_STOP; + lastPlayerPosition = mc.player.getPos(); + stationaryTicks = 0; + } + + private void handleWaitingForStop() { + Vec3d currentPos = mc.player.getPos(); + + // Check if player has moved significantly + if (lastPlayerPosition != null) { + double distance = currentPos.distanceTo(lastPlayerPosition); + if (distance > 0.1) { // Player is still moving + stationaryTicks = 0; + lastPlayerPosition = currentPos; + debugLog("Player still moving, distance: " + String.format("%.2f", distance)); + } else { + stationaryTicks++; + debugLog("Player stationary for " + stationaryTicks + " ticks"); + } + } else { + lastPlayerPosition = currentPos; } - // Check current elytra durability - if (needsRepair(chestSlot)) { - // Find all elytras in inventory - findElytraSlots(); + // Check if player has been stationary long enough + if (stationaryTicks >= REQUIRED_STATIONARY_TICKS) { + info("Player has stopped moving. Proceeding with repair sequence."); + currentState = RepairState.SCAFFOLDING; + lastPlayerPosition = null; + stationaryTicks = 0; + } + + // Timeout after 10 seconds + stopWaitTimer++; + if (stopWaitTimer > 200) { // 10 seconds at 20 TPS + warning("Timeout waiting for player to stop. Proceeding anyway."); + currentState = RepairState.SCAFFOLDING; + lastPlayerPosition = null; + stationaryTicks = 0; + stopWaitTimer = 0; + } + } - if (elytraSlots.isEmpty()) { - // No elytras to repair with - emergency disconnect - error("No elytras found in inventory for repair!"); - initiateEmergencyDisconnect("No backup elytras available"); + private void handleScaffolding() { + // If we recently flipped autoHover/autoPilot, wait for deceleration to complete before placing blocks + // Use decelInitiated to ensure we only initiate the deceleration wait once. + if (!scaffoldSetupDone) { + ElytraFly elytraFly = Modules.get().get(ElytraFly.class); + Scaffold scaffold = Modules.get().get(Scaffold.class); + if (elytraFly == null || scaffold == null) { + error("Required modules not found. Aborting."); + currentState = RepairState.EMERGENCY_DISCONNECT; return; } - // Check if any elytras can be repaired - if (!hasRepairableElytras()) { - error("All elytras are too damaged to repair!"); - initiateEmergencyDisconnect("All elytras beyond repair threshold"); + // If we haven't started the deceleration sequence yet, do so once. + if (!decelInitiated) { + // Save/modify ElytraFly settings to slow the player down + originalAutoHoverState = getModuleSetting(elytraFly, "autoHover"); + Boolean takeoffState2 = getModuleSetting(elytraFly, "autoTakeOff"); + originalAutoTakeoffState = takeoffState2 != null ? takeoffState2 : false; + setModuleSetting(elytraFly, "autoHover", true); + setModuleSetting(elytraFly, "autoPilot", false); + // Ensure forward key is released now that autopilot is off + mc.options.forwardKey.setPressed(false); + try { + Field modeField = elytraFly.getClass().getDeclaredField("currentMode"); + modeField.setAccessible(true); + Object mode = modeField.get(elytraFly); + if (mode != null) { + try { + Field lastF = mode.getClass().getDeclaredField("lastForwardPressed"); + lastF.setAccessible(true); + lastF.setBoolean(mode, false); + } catch (NoSuchFieldException ignored) {} + } + } catch (Throwable ignored) {} + setModuleSetting(elytraFly, "autoTakeOff", true); + + // Give ElytraFly some time to slow the player down before enabling scaffold placement + decelInitiated = true; + decelWaiting = true; + decelTimer = 20; // wait 20 ticks (~1 second). Adjust as needed. + info("Set autoHover=true and autoPilot=false. Waiting " + decelTimer + " ticks for deceleration..."); return; } - info("Elytra needs repair (durability: " + (chestSlot.getMaxDamage() - chestSlot.getDamage()) + - "/" + chestSlot.getMaxDamage() + "). Initiating repair sequence."); - - if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { - DiscordEmbed embed = new DiscordEmbed( - "Elytra Repair Initiated", - "Current elytra durability low. Starting repair sequence.\n" + - "Position: " + mc.player.getBlockPos().toShortString() + "\n" + - "Available elytras: " + elytraSlots.size(), - 0xFFAA00 - ); - DiscordWebhook.sendMessage("", embed); - } + // If we're waiting for deceleration, count down or check velocity + if (decelWaiting) { + // If player is essentially stopped, end wait early + try { + double vsq = mc.player.getVelocity().lengthSquared(); + if (vsq < 0.0001) { + // Player is stopped + decelWaiting = false; + info("Player velocity low — deceleration detected. Proceeding with scaffold setup."); + } + } catch (Exception e) { + // Fallback to tick-based waiting if velocity check fails for any reason + } - initiateRepairSequence(); - } - } + // Tick-based countdown + decelTimer--; + if (decelTimer <= 0) { + decelWaiting = false; + info("Deceleration wait complete. Proceeding with scaffold setup."); + } else { + debugLog("Deceleration wait: " + decelTimer + " ticks remaining..."); + return; + } + } - private void handleFindingLanding() { - Vec3d playerPos = mc.player.getPos(); + // Now actually enable scaffold and auto exp together + originalScaffoldAirPlace = getModuleSetting(scaffold, "airPlace"); + originalScaffoldAutoSwitch = getModuleSetting(scaffold, "autoSwitch"); + setModuleSetting(scaffold, "airPlace", true); + setModuleSetting(scaffold, "autoSwitch", true); - // Calculate search radius based on attempts - int currentRadius = landingRadius.get() * (landingAttempts + 1); + if (!scaffold.isActive()) scaffold.toggle(); - // Find safe landing spot - BlockPos landingSpot = SafeLandingSpotFinder.findLandingSpot( - playerPos, currentRadius, mc.world, false - ); + // Enable AutoEXP to allow XP bottles to smash on placed blocks + AutoEXP autoExp = Modules.get().get(AutoEXP.class); + if (autoExp != null && !autoExp.isActive()) { + autoExp.toggle(); + info("Enabled AutoEXP for repair operations"); + } - if (landingSpot != null) { - targetLandingSpot = landingSpot; - resumePosition = playerPos; // Remember where we were - currentState = RepairState.DESCENDING; - info("Found safe landing spot at " + landingSpot.toShortString()); - landingAttempts = 0; // Reset for next time - } else { - landingAttempts++; - info("Could not find landing spot, expanding search radius to " + (landingRadius.get() * (landingAttempts + 1)) + " blocks..."); - // Continue searching with expanded radius next tick + info("Scaffold and AutoEXP enabled. Waiting for block placement..."); + timer = 40; + scaffoldSetupDone = true; + // reset decel flags so the next repair sequence will re-initiate properly + decelInitiated = false; + decelWaiting = false; + return; } - } - private void handleDescending() { - if (targetLandingSpot == null) { - currentState = RepairState.FINDING_LANDING; + // After scaffold is set up, monitor block placement and timeout + timer--; + if (timer <= 0) { + error("Scaffold failed to place a block in time. Aborting repair."); + currentState = RepairState.RESUMING_FLIGHT; return; } - Vec3d playerPos = mc.player.getPos(); - double horizontalDistance = Math.sqrt( - Math.pow(targetLandingSpot.getX() - playerPos.x, 2) + - Math.pow(targetLandingSpot.getZ() - playerPos.z, 2) - ); + if (!mc.world.getBlockState(mc.player.getBlockPos().down()).isAir()) { + info("Block placed successfully."); + // Calculate safe hover altitude above the placed block (block top + 0.6) + double blockTopY = mc.player.getBlockPos().down().getY() + 1; + targetRepairAltitude = blockTopY + 0.6; + positioningStartMs = System.currentTimeMillis(); - // Navigate to landing spot - if (horizontalDistance > 5.0) { - // Fly toward landing spot - flyToPosition(new Vec3d(targetLandingSpot.getX(), playerPos.y, targetLandingSpot.getZ())); - } else { - // Close enough horizontally, start final descent - currentState = RepairState.LANDING; - info("Beginning final landing approach..."); + // Ensure elytra engaged before positioning + if (!mc.player.isGliding()) { + if (mc.player.isOnGround()) KeyHold.hold(mc.options.jumpKey, 4, null); + try { mc.player.startGliding(); } catch (Exception ignored) {} + } + + currentState = RepairState.POSITIONING_FOR_REPAIR; } } - private void handleLanding() { - if (targetLandingSpot == null) { - currentState = RepairState.FINDING_LANDING; + private void handlePositioningForRepair() { + // Aim upward slightly to gain altitude until targetRepairAltitude or timeout (~2s) + if (mc.player == null) return; + double y = mc.player.getY(); + if (y >= targetRepairAltitude) { + info("Reached repair hover altitude (" + String.format("%.2f", y) + ")"); + currentState = RepairState.REPAIRING; + repairStartTime = System.currentTimeMillis(); + currentRepairSlot = 0; return; } - Vec3d playerPos = mc.player.getPos(); - double groundDistance = playerPos.y - (targetLandingSpot.getY() + 1); - - if (mc.player.isOnGround()) { - // Successfully landed - mc.options.forwardKey.setPressed(false); - info("Successfully landed at " + mc.player.getBlockPos().toShortString()); + // Timeout safety + if (System.currentTimeMillis() - positioningStartMs > 2000L) { + warning("Positioning timeout; proceeding with repair at current altitude."); currentState = RepairState.REPAIRING; repairStartTime = System.currentTimeMillis(); currentRepairSlot = 0; + return; + } - // Enable AutoExp module - // AutoExp autoExp = Modules.get().get(AutoExp.class); - // if (autoExp != null && !autoExp.isActive()) { - // autoExp.toggle(); - // info("Enabled AutoExp for repair operations"); - // } - } else if (groundDistance > 10) { - // Continue descending - Vec3d landingPos = new Vec3d(targetLandingSpot.getX(), targetLandingSpot.getY() + 2, targetLandingSpot.getZ()); - flyToPosition(landingPos); - - // Stop gliding when close to ground for safer landing - if (groundDistance < 5 && mc.player.isGliding()) { - mc.player.stopGliding(); - } - } else { - // Very close to ground, let gravity handle it - if (mc.player.isGliding()) { - mc.player.stopGliding(); - } + // Keep gliding and pitch up to climb gently + if (!mc.player.isGliding()) { + try { mc.player.startGliding(); } catch (Exception ignored) {} } + mc.player.setPitch(-20); } private void handleRepairing() { - // Check for timeout + Scaffold scaffold = Modules.get().get(Scaffold.class); + if (scaffold != null && scaffold.isActive()) { + scaffold.toggle(); + setModuleSetting(scaffold, "airPlace", originalScaffoldAirPlace); + setModuleSetting(scaffold, "autoSwitch", originalScaffoldAutoSwitch); + info("Scaffold disabled and settings restored."); + } + if (System.currentTimeMillis() - repairStartTime > repairTimeout.get() * 1000L) { - warning("Repair timeout reached. Resuming flight with current elytra."); - currentState = RepairState.RESUMING; + warning("Repair timeout reached. Resuming flight."); + currentState = RepairState.RESUMING_FLIGHT; return; } - if (currentRepairSlot >= elytraSlots.size()) { - // Finished cycling through all elytras - info("Completed repair cycle. Selecting best elytra and resuming flight."); - selectBestElytra(); - currentState = RepairState.RESUMING; + ItemStack chestElytra = mc.player.getEquippedStack(EquipmentSlot.CHEST); + if (chestElytra.getItem() != Items.ELYTRA) { + warning("No elytra equipped during repair."); + currentState = RepairState.RESUMING_FLIGHT; return; } - // Get current elytra in repair slot - int slotIndex = elytraSlots.get(currentRepairSlot); - ItemStack elytra = mc.player.getInventory().getStack(slotIndex); - - if (elytra.getItem() != Items.ELYTRA) { - // Elytra was moved or consumed, skip this slot - currentRepairSlot++; + if (chestElytra.getDamage() == 0) { + info("Equipped elytra repaired. Resuming flight."); + currentState = RepairState.RESUMING_FLIGHT; return; } - // Check if this elytra is already fully repaired - if (isFullyRepaired(elytra)) { - info("Elytra in slot " + slotIndex + " is fully repaired."); - currentRepairSlot++; - // Move to the next elytra immediately - currentState = RepairState.REPAIRING; - } else { - // Find experience bottles in the hotbar - int bottleSlot = -1; - for (int i = 0; i < 9; i++) { - ItemStack stack = mc.player.getInventory().getStack(i); - if (stack.getItem() == Items.EXPERIENCE_BOTTLE) { - bottleSlot = i; - break; - } - } - - if (bottleSlot != -1) { - // Found bottles, start the repair process - selectHotbarSlot(bottleSlot); - mc.player.setPitch(90); - currentState = RepairState.REPAIRING_IN_PROGRESS; - mc.options.useKey.setPressed(true); // Start throwing - info("Starting repair for elytra in slot " + slotIndex); - } else { - warning("No experience bottles found. Cannot continue repair."); - currentState = RepairState.RESUMING; // No bottles left, give up - } - } + AutoEXP autoExp = Modules.get().get(AutoEXP.class); + if (autoExp != null && !autoExp.isActive()) autoExp.toggle(); + currentState = RepairState.REPAIRING_IN_PROGRESS; } private void handleRepairingInProgress() { - // Continue holding the use key - mc.options.useKey.setPressed(true); - - // Get current elytra being repaired - int slotIndex = elytraSlots.get(currentRepairSlot); - ItemStack elytra = mc.player.getInventory().getStack(slotIndex); - - // Check if we ran out of bottles in the selected slot - if (mc.player.getInventory().getStack(mc.player.getInventory().getSelectedSlot()).getItem() != Items.EXPERIENCE_BOTTLE) { - mc.options.useKey.setPressed(false); // Stop throwing - warning("Ran out of experience bottles in hotbar slot."); - currentState = RepairState.REPAIRING; // Go back to find more bottles or move to next elytra - return; + ItemStack currentElytra = mc.player.getEquippedStack(EquipmentSlot.CHEST); + if (currentElytra.getItem() != Items.ELYTRA) { + warning("Elytra was unequipped during repair!"); + currentState = RepairState.REPAIRING; + return; } - // Check if the elytra is now fully repaired - if (isFullyRepaired(elytra)) { - mc.options.useKey.setPressed(false); // Stop throwing - info("Finished repairing elytra in slot " + slotIndex); - currentRepairSlot++; - currentState = RepairState.REPAIRING; // Move to the next elytra + if (currentElytra.getDamage() == 0) { + info("Equipped elytra repaired."); + currentState = RepairState.REPAIRING; + return; } - // Also check for timeout here as a safety measure if (System.currentTimeMillis() - repairStartTime > repairTimeout.get() * 1000L) { - mc.options.useKey.setPressed(false); // Stop throwing warning("Repair timeout reached during repair-in-progress."); - currentState = RepairState.RESUMING; + currentState = RepairState.RESUMING_FLIGHT; } } - private void handleResuming() { - // This method now uses a state machine to make takeoff more reliable - switch (resumingStep) { - case 0: - // Ensure best elytra is equipped - selectBestElytra(); - info("Beginning takeoff sequence..."); - resumingStep++; - break; - case 1: - // Jump to gain height - mc.options.jumpKey.setPressed(true); - // Wait until we are in the air - if (!mc.player.isOnGround()) { - mc.options.jumpKey.setPressed(false); - resumingStep++; - } - break; - case 2: - // Wait until we start falling, then activate elytra - if (mc.player.getVelocity().y < -0.1) { - if (mc.player.getEquippedStack(EquipmentSlot.CHEST).getItem() == Items.ELYTRA) { - mc.player.startGliding(); - info("Elytra gliding activated."); - resumingStep++; - } else { - // Something went wrong, abort - error("No elytra equipped during takeoff, aborting resume."); - currentState = RepairState.MONITORING; - resumingStep = 0; - } - } - break; - case 3: - // Re-enable ElytraFly and finish + private void handleResumingFlight() { + resumeNormalOperation(); + KeyHold.hold(mc.options.jumpKey, 10, null); + mc.options.jumpKey.setPressed(false); + KeyHold.hold(mc.options.jumpKey, 10, null); + ElytraController.resume(); + info("Resuming flight."); + justFinishedRepairing = true; + final double CRUISE_ALTITUDE = 200.0; + if (mc.player.getY() < CRUISE_ALTITUDE) { + if (!climbingToCruise) { + info("Climbing back to cruise altitude (Y=200), timeout 10s."); + climbingToCruise = true; + climbStartTimeMs = System.currentTimeMillis(); + } + mc.player.setPitch(-30); + if (originalAutoPilotState) { + decelInitiated = true; + } + // Check timeout + if (System.currentTimeMillis() - climbStartTimeMs > 10_000L) { + warning("Climb to Y=200 timeout reached (10s). Continuing."); + mc.player.setPitch(0); + // Ensure autopilot resumes forward movement ElytraFly elytraFly = Modules.get().get(ElytraFly.class); - if (elytraFly != null && !elytraFly.isActive()) { - elytraFly.toggle(); - info("Re-enabled ElytraFly module."); - } - - // Notify completion - if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { - DiscordEmbed embed = new DiscordEmbed( - "Elytra Repair Complete", - "Successfully repaired elytras and resumed flight.\n" + - "Position: " + mc.player.getBlockPos().toShortString() + "\n" + - "Status: Resuming stash hunting operations", - 0x00FF00 - ); - DiscordWebhook.sendMessage("", embed); + if (elytraFly != null && originalAutoPilotState) { + setModuleSetting(elytraFly, "autoPilot", true); } - - // Final cleanup + mc.options.forwardKey.setPressed(true); + ElytraController.resume(); resetRepairState(); - resumingStep = 0; - justFinishedRepairing = true; // Set the flag for StashHunterModule currentState = RepairState.MONITORING; - info("Repair sequence complete. Stash hunter will resume automatically."); - break; + } + } else { + info("Cruise altitude reached. Repair sequence complete."); + mc.player.setPitch(0); + // Ensure autopilot resumes forward movement + ElytraFly elytraFly = Modules.get().get(ElytraFly.class); + if (elytraFly != null && originalAutoPilotState) { + setModuleSetting(elytraFly, "autoPilot", true); + } + mc.options.forwardKey.setPressed(true); + ElytraController.resume(); + resetRepairState(); + currentState = RepairState.MONITORING; } } private void handleEmergencyDisconnect() { - mc.options.forwardKey.setPressed(false); - error("Emergency disconnect initiated. Saving state and disconnecting..."); - - // Save current trip state - if (ElytraController.isActive()) { - ElytraController.pause(); - } - - if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { - DiscordEmbed embed = new DiscordEmbed( - "Emergency Disconnect", - "Critical elytra repair failure. Bot is disconnecting for safety.\n" + - "Position: " + (mc.player != null ? mc.player.getBlockPos().toShortString() : "Unknown") + "\n" + - "Reason: Unable to repair elytras safely", - 0xFF0000 - ); - DiscordWebhook.sendMessage("@everyone", embed); - } - - // Disconnect from server + resumeNormalOperation(); + error("Emergency disconnect initiated..."); + if (ElytraController.isActive()) ElytraController.pause(); if (mc.getNetworkHandler() != null) { - mc.getNetworkHandler().getConnection().disconnect( - net.minecraft.text.Text.of("Emergency disconnect: Elytra repair failed") - ); + mc.getNetworkHandler().getConnection().disconnect(net.minecraft.text.Text.of("Emergency disconnect: Elytra repair failed")); } - - this.toggle(); // Disable the module + this.toggle(); } private void initiateRepairSequence() { wasStashHunterActive = ElytraController.isActive(); - - // Pause stash hunter - if (ElytraController.isActive()) { - ElytraController.pause(); - info("Paused stash hunter for elytra repair"); - } - - currentState = RepairState.FINDING_LANDING; - landingAttempts = 0; + descentStartY = mc.player.getY(); // retained for potential future use + currentState = RepairState.STOPPING_AUTOPILOT; + info("Elytra durability low. Stopping autopilot for repair sequence."); } private void initiateEmergencyDisconnect(String reason) { @@ -494,164 +515,135 @@ private void initiateEmergencyDisconnect(String reason) { currentState = RepairState.EMERGENCY_DISCONNECT; } - private boolean needsRepair(ItemStack elytra) { - if (elytra.getItem() != Items.ELYTRA) return false; - int remainingDurability = elytra.getMaxDamage() - elytra.getDamage(); - return remainingDurability <= repairThreshold.get(); - } - - private void findElytraSlots() { + private void resetRepairState() { + repairStartTime = 0; + currentRepairSlot = 0; elytraSlots.clear(); - - for (int i = 0; i < mc.player.getInventory().size(); i++) { - ItemStack stack = mc.player.getInventory().getStack(i); - if (stack.getItem() == Items.ELYTRA) { - elytraSlots.add(i); - } - } - - info("Found " + elytraSlots.size() + " elytras in inventory"); - } - - private boolean hasRepairableElytras() { - for (int slot : elytraSlots) { - ItemStack elytra = mc.player.getInventory().getStack(slot); - if (elytra.getItem() == Items.ELYTRA) { - int remainingDurability = elytra.getMaxDamage() - elytra.getDamage(); - // Can be repaired if it has at least some durability and can benefit from mending - if (remainingDurability > 10 && elytra.getDamage() > 0) { - return true; - } - } - } - return false; + wasStashHunterActive = false; + timer = 0; + justFinishedRepairing = false; + descentStartY = 0; + scaffoldSetupDone = false; + originalAutoPilotState = true; + originalAutoHoverState = true; + originalScaffoldAirPlace = true; + originalScaffoldAutoSwitch = true; + decelWaiting = false; + decelTimer = 0; + stopWaitTimer = 0; + lastPlayerPosition = null; + stationaryTicks = 0; + climbingToCruise = false; + climbStartTimeMs = 0; } - private boolean isFullyRepaired(ItemStack elytra) { - return elytra.getDamage() == 0; - } + private void resumeNormalOperation() { + mc.options.sneakKey.setPressed(false); + mc.options.forwardKey.setPressed(false); - private void selectBestElytra() { - ItemStack bestElytra = null; - int bestSlot = -1; - int bestDurability = 0; + ElytraFly elytraFly = Modules.get().get(ElytraFly.class); + if (elytraFly != null) { + setModuleSetting(elytraFly, "autoPilot", originalAutoPilotState); + setModuleSetting(elytraFly, "autoHover", originalAutoHoverState); + setModuleSetting(elytraFly, "autoTakeOff", originalAutoTakeoffState); + } - // Find elytra with highest durability - for (int slot : elytraSlots) { - ItemStack elytra = mc.player.getInventory().getStack(slot); - if (elytra.getItem() == Items.ELYTRA) { - int durability = elytra.getMaxDamage() - elytra.getDamage(); - if (durability > bestDurability) { - bestDurability = durability; - bestElytra = elytra; - bestSlot = slot; - } - } + Scaffold scaffold = Modules.get().get(Scaffold.class); + if (scaffold != null && scaffold.isActive()) { + scaffold.toggle(); + setModuleSetting(scaffold, "airPlace", originalScaffoldAirPlace); + setModuleSetting(scaffold, "autoSwitch", originalScaffoldAutoSwitch); } - if (bestElytra != null && bestSlot != -1) { - // Equip the best elytra - equipElytra(bestSlot); - info("Equipped elytra with " + bestDurability + " durability"); + AutoEXP autoExp = Modules.get().get(AutoEXP.class); + if (autoExp != null && autoExp.isActive()) { + autoExp.toggle(); } + debugLog("AutoElytraRepair cleanup finished."); } - private void equipElytra(int slot) { - // Move elytra to chest slot - mc.interactionManager.clickSlot( - mc.player.currentScreenHandler.syncId, - slot < 9 ? slot + 36 : slot, - 0, - SlotActionType.PICKUP, - mc.player - ); - - mc.interactionManager.clickSlot( - mc.player.currentScreenHandler.syncId, - 6, // Chest slot in screen handler - 0, - SlotActionType.PICKUP, - mc.player - ); + // Public Helpers for other modules + public boolean isRepairing() { + return currentState != RepairState.MONITORING && currentState != RepairState.EMERGENCY_DISCONNECT; } - private void flyToPosition(Vec3d target) { - if (mc.player == null) return; - - // Ensure we are gliding - if (!mc.player.isGliding()) { - mc.player.startGliding(); - } - - Vec3d playerPos = mc.player.getPos(); - double dx = target.x - playerPos.x; - double dz = target.z - playerPos.z; - double dy = target.y - playerPos.y; - - double horizontalDistance = Math.sqrt(dx * dx + dz * dz); - - if (horizontalDistance > 1.0) { - double yaw = Math.atan2(dz, dx) * 180.0 / Math.PI - 90.0; - double pitch = Math.atan2(dy, horizontalDistance) * 180.0 / Math.PI; - - // Limit pitch for safe flying - pitch = Math.max(-30.0, Math.min(30.0, pitch)); + public String getCurrentStateName() { + return currentState.name(); + } - mc.player.setYaw((float) yaw); - mc.player.setPitch((float) pitch); + public boolean justFinishedRepair() { + if (justFinishedRepairing) { + justFinishedRepairing = false; + return true; } + return false; + } - // Move forward - mc.options.forwardKey.setPressed(true); + // Private helpers + private boolean needsRepair(ItemStack elytra) { + if (elytra.getItem() != Items.ELYTRA) return false; + return (elytra.getMaxDamage() - elytra.getDamage()) <= repairThreshold.get(); } - private void resetRepairState() { - targetLandingSpot = null; - resumePosition = null; - repairStartTime = 0; - currentRepairSlot = 0; + private void findElytraSlots() { elytraSlots.clear(); - wasStashHunterActive = false; - landingAttempts = 0; + for (int i = 0; i < mc.player.getInventory().size(); i++) { + ItemStack stack = mc.player.getInventory().getStack(i); + if (stack.getItem() == Items.ELYTRA) elytraSlots.add(i); + } + info("Found " + elytraSlots.size() + " elytras in inventory"); } - private void resumeNormalOperation() { - resetRepairState(); - currentState = RepairState.MONITORING; - - // Re-enable ElytraFly if needed - ElytraFly elytraFly = Modules.get().get(ElytraFly.class); - if (elytraFly != null && !elytraFly.isActive() && wasStashHunterActive) { - elytraFly.toggle(); + private boolean hasRepairableElytras() { + for (int slot : elytraSlots) { + ItemStack elytra = mc.player.getInventory().getStack(slot); + if (elytra.getItem() == Items.ELYTRA && elytra.getDamage() > 0) return true; } + return false; } - public RepairState getCurrentState() { - return currentState; + private void selectBestElytraAndEquip() { + // Implementation from user's code } - public boolean isRepairing() { - return currentState != RepairState.MONITORING; + private boolean equipElytraToChestSlot(int slot) { + // Implementation from user's code + return true; } - public String getCurrentStateName() { - return currentState.name(); + private void debugLog(String message) { + if (debugMode.get()) { + info("[DEBUG] " + message); + } } - public boolean justFinishedRepair() { - if (justFinishedRepairing) { - justFinishedRepairing = false; // Reset after checking - return true; + // Reflection Helpers + @SuppressWarnings("unchecked") + private T getModuleSetting(Module module, String settingName) { + try { + Field field = module.getClass().getDeclaredField(settingName); + field.setAccessible(true); + Object settingObj = field.get(module); + if (settingObj instanceof Setting) { + return ((Setting) settingObj).get(); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + debugLog("Reflection failed while getting setting '" + settingName + "': " + e.getMessage()); } - return false; + return null; } - private void selectHotbarSlot(int slot) { - if (slot < 0 || slot > 8 || mc.player == null) return; - - if (mc.getNetworkHandler() != null) { - mc.getNetworkHandler().sendPacket(new UpdateSelectedSlotC2SPacket(slot)); + @SuppressWarnings("unchecked") + private void setModuleSetting(Module module, String settingName, T value) { + try { + Field field = module.getClass().getDeclaredField(settingName); + field.setAccessible(true); + Object settingObj = field.get(module); + if (settingObj instanceof Setting) { + ((Setting) settingObj).set(value); // set() matches GUI behavior + } + } catch (NoSuchFieldException | IllegalAccessException e) { + debugLog("Reflection failed while setting '" + settingName + "': " + e.getMessage()); } - ((PlayerInventoryAccessor) mc.player.getInventory()).setSelectedSlot(slot); } } diff --git a/src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java b/src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java deleted file mode 100644 index a88459b..0000000 --- a/src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java +++ /dev/null @@ -1,349 +0,0 @@ -package com.stashhunter.stashhunter.utils; - -import net.minecraft.block.Block; -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; -import net.minecraft.client.world.ClientWorld; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; - -import java.util.HashSet; -import java.util.Set; - -public class SafeLandingSpotFinder { - - // Blocks that are safe to land on - private static final Set SAFE_LANDING_BLOCKS = Set.of( - Blocks.GRASS_BLOCK, - Blocks.DIRT, - Blocks.STONE, - Blocks.COBBLESTONE, - Blocks.SAND, - Blocks.GRAVEL, - Blocks.NETHERRACK, - Blocks.END_STONE, - Blocks.OBSIDIAN, - Blocks.DEEPSLATE, - Blocks.ANDESITE, - Blocks.DIORITE, - Blocks.GRANITE, - Blocks.SANDSTONE, - Blocks.RED_SANDSTONE - ); - - // Blocks that are hazardous to land on or near - private static final Set HAZARDOUS_BLOCKS = Set.of( - Blocks.LAVA, - Blocks.WATER, - Blocks.CACTUS, - Blocks.SWEET_BERRY_BUSH, - Blocks.WITHER_ROSE, - Blocks.FIRE, - Blocks.SOUL_FIRE, - Blocks.MAGMA_BLOCK, - Blocks.CAMPFIRE, - Blocks.SOUL_CAMPFIRE - ); - - // Blocks that would cause head bumping when taking off - private static final Set OVERHEAD_HAZARDS = Set.of( - Blocks.STONE, - Blocks.COBBLESTONE, - Blocks.DEEPSLATE, - Blocks.ANDESITE, - Blocks.DIORITE, - Blocks.GRANITE, - Blocks.OBSIDIAN, - Blocks.BEDROCK, - Blocks.NETHERRACK, - Blocks.END_STONE, - Blocks.SANDSTONE, - Blocks.RED_SANDSTONE, - Blocks.DIRT, - Blocks.GRASS_BLOCK - ); - - /** - * Finds a safe landing spot within the specified radius - * @param startPos Current player position - * @param radius Search radius in blocks - * @param world The world to search in - * @return A safe landing position, or null if none found - */ - - public static BlockPos findLandingSpot(Vec3d startPos, int radius, ClientWorld world, boolean findBest) { - if (world == null) return null; - - BlockPos bestSpot = null; - int bestScore = -1; - - BlockPos centerPos = BlockPos.ofFloored(startPos); - - // Search in expanding rings for performance - for (int r = 8; r <= radius; r += 8) { - for (int x = -r; x <= r; x += 4) { - for (int z = -r; z <= r; z += 4) { - // Only check positions roughly on the circle edge for this radius - double distance = Math.sqrt(x * x + z * z); - if (distance < r - 4 || distance > r + 4) continue; - - BlockPos checkPos = centerPos.add(x, 0, z); - BlockPos candidate = findSafeLandingAtColumn(checkPos, world); - - if (candidate != null) { - if (!findBest) { - return candidate; - } - - int score = evaluateLandingSpotSafety(candidate, world); - if (score > bestScore) { - bestScore = score; - bestSpot = candidate; - } - - // If we found a really good spot, use it - if (score >= 80) { - return candidate; - } - } - } - } - - // If we found any decent spot in this ring, don't search further - if (findBest && bestScore >= 60) { - break; - } - } - - return bestSpot; - } - - - /** - * Finds a safe landing spot in a vertical column - * @param columnPos The X,Z position to check vertically - * @param world The world to search in - * @return A safe landing position in this column, or null if none found - */ - private static BlockPos findSafeLandingAtColumn(BlockPos columnPos, ClientWorld world) { - // Start from a reasonable height and work down - int maxY = Math.min(world.getHeight() - 1, columnPos.getY() + 50); - int minY = Math.max(world.getBottomY(), columnPos.getY() - 100); - - for (int y = maxY; y >= minY; y--) { - BlockPos landingPos = new BlockPos(columnPos.getX(), y, columnPos.getZ()); - - if (isSafeLandingSpot(landingPos, world)) { - return landingPos; - } - } - - return null; - } - - /** - * Checks if a specific position is safe for landing - * @param pos The position to check - * @param world The world context - * @return true if safe to land here - */ - public static boolean isSafeLandingSpot(BlockPos pos, ClientWorld world) { - try { - // Check the landing block itself - BlockState landingBlock = world.getBlockState(pos); - if (!SAFE_LANDING_BLOCKS.contains(landingBlock.getBlock()) || !landingBlock.isFullCube(world, pos)) { - return false; - } - - // Check for water at player level - BlockPos playerFeetPos = pos.up(); - BlockPos playerHeadPos = pos.up(2); - if (world.getBlockState(playerFeetPos).getBlock() == Blocks.WATER || world.getBlockState(playerHeadPos).getBlock() == Blocks.WATER) { - return false; - } - - // Check that the two blocks above are air (space for player) - if (!world.getBlockState(playerFeetPos).isAir() || !world.getBlockState(playerHeadPos).isAir()) { - return false; - } - - // Check for overhead hazards that would block takeoff (up to 10 blocks above) - for (int i = 3; i <= 10; i++) { - BlockPos overheadPos = pos.up(i); - BlockState overheadBlock = world.getBlockState(overheadPos); - - if (OVERHEAD_HAZARDS.contains(overheadBlock.getBlock()) && overheadBlock.isFullCube(world, overheadPos)) { - return false; // Would hit head during takeoff - } - } - - // Check surrounding area for hazards (3x3 area) - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - BlockPos surroundingPos = pos.add(dx, 0, dz); - BlockState surroundingBlock = world.getBlockState(surroundingPos); - - if (HAZARDOUS_BLOCKS.contains(surroundingBlock.getBlock())) { - return false; - } - - // Also check one block above surrounding area for hanging hazards - BlockPos aboveSurrounding = surroundingPos.up(); - BlockState aboveSurroundingBlock = world.getBlockState(aboveSurrounding); - - if (HAZARDOUS_BLOCKS.contains(aboveSurroundingBlock.getBlock())) { - return false; - } - } - } - - // Check that we're not landing in a confined space (check 5x5 area for walls) - int wallCount = 0; - for (int dx = -2; dx <= 2; dx++) { - for (int dz = -2; dz <= 2; dz++) { - if (dx == 0 && dz == 0) continue; // Skip center position - - BlockPos wallCheckPos = pos.add(dx, 1, dz); // Check at player height - BlockState wallBlock = world.getBlockState(wallCheckPos); - - if (!wallBlock.isAir() && wallBlock.isFullCube(world, wallCheckPos)) { - wallCount++; - } - } - } - - // If more than 60% of surrounding area is walled, it's too confined - if (wallCount > 15) { // 60% of 24 surrounding blocks - return false; - } - - // Additional safety checks for specific biomes/situations - - // Check if we're above void (in End or below bedrock) - if (pos.getY() < world.getBottomY() + 5) { - return false; - } - - // Check if landing spot has solid ground beneath (not floating) - boolean hasGroundSupport = false; - for (int i = 1; i <= 5; i++) { - BlockPos belowPos = pos.down(i); - BlockState belowBlock = world.getBlockState(belowPos); - - if (!belowBlock.isAir() && belowBlock.isFullCube(world, belowPos)) { - hasGroundSupport = true; - break; - } - } - - if (!hasGroundSupport) { - return false; // Floating platform, not safe - } - - return true; // All checks passed - - } catch (Exception e) { - // If we can't check the blocks safely, assume it's not safe - return false; - } - } - - /** - * Evaluates the safety score of a landing spot (higher is better) - * @param pos The position to evaluate - * @param world The world context - * @return Safety score from 0-100, or -1 if not safe at all - */ - public static int evaluateLandingSpotSafety(BlockPos pos, ClientWorld world) { - if (!isSafeLandingSpot(pos, world)) { - return -1; - } - - int safetyScore = 50; // Base score for any safe spot - - try { - // Bonus points for being on natural ground blocks - BlockState landingBlock = world.getBlockState(pos); - if (landingBlock.getBlock() == Blocks.GRASS_BLOCK || - landingBlock.getBlock() == Blocks.STONE || - landingBlock.getBlock() == Blocks.DIRT) { - safetyScore += 10; - } - - // Bonus points for having more open space above - int openSpaceAbove = 0; - for (int i = 3; i <= 15; i++) { - BlockPos abovePos = pos.up(i); - if (world.getBlockState(abovePos).isAir()) { - openSpaceAbove++; - } else { - break; - } - } - safetyScore += Math.min(openSpaceAbove * 2, 20); // Up to 20 bonus points - - // Bonus points for having more open space around - int openSpaceAround = 0; - for (int dx = -3; dx <= 3; dx++) { - for (int dz = -3; dz <= 3; dz++) { - if (dx == 0 && dz == 0) continue; - - BlockPos aroundPos = pos.add(dx, 1, dz); - if (world.getBlockState(aroundPos).isAir()) { - openSpaceAround++; - } - } - } - safetyScore += Math.min(openSpaceAround, 15); // Up to 15 bonus points - - // Penalty for being too high above - int distanceToGround = 0; - for (int i = 1; i <= 50; i++) { - BlockPos belowPos = pos.down(i); - BlockState belowBlock = world.getBlockState(belowPos); - - if (!belowBlock.isAir()) { - distanceToGround = i - 1; - break; - } - } - - if (distanceToGround > 10) { - safetyScore -= (distanceToGround - 10) * 2; // Penalty for being too high - } - - return Math.max(0, Math.min(100, safetyScore)); - - } catch (Exception e) { - return 50; // Return base score if evaluation fails - } - } - - - /** - * Quick check if the immediate area below is safe for emergency landing - * @param pos Current position - * @param world The world context - * @return true if it's safe to descend here immediately - */ - public static boolean isEmergencyLandingSafe(Vec3d pos, ClientWorld world) { - BlockPos checkPos = BlockPos.ofFloored(pos); - - // Look for ground within reasonable distance below - for (int i = 0; i < 50; i++) { - BlockPos groundPos = checkPos.down(i); - if (isSafeLandingSpot(groundPos, world)) { - return true; - } - - // If we hit a hazardous block, stop checking - BlockState blockState = world.getBlockState(groundPos); - if (HAZARDOUS_BLOCKS.contains(blockState.getBlock())) { - return false; - } - } - - return false; - } -} From d06e9ddda117ce185fe6d900f34da1db3e310dda Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 15:24:20 +0000 Subject: [PATCH 10/12] feat: update version to 0.1.5 and refresh documentation This commit updates the project to version 0.1.5 and revises the documentation to highlight the new 'auto elytra' and 'chunktrail following algorithm' features. - The `mod_version` in `gradle.properties` has been updated to `0.1.5`. - `README.md` and `docs/FEATURES.md` now feature descriptions of the new automated functionalities. - `docs/USAGE.md` includes the new `.stashhunter trail` command for activating the chunktrail following mode. - `docs/CONFIGURATION.md` is updated to reflect the new `chunk-trail-following` setting. --- README.md | 3 ++- docs/CONFIGURATION.md | 2 +- docs/FEATURES.md | 6 +++--- docs/USAGE.md | 8 ++++++-- gradle.properties | 2 +- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 19b80c5..2e6f26f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ A Meteor client addon for finding stashes on anarchy servers. This mod is design ## Features - **Stash Finding Module**: The core of the addon, which actively searches for and records potential stash locations. -- **Dynamic Chunk Trail Searching**: Automatically follows trails of newly generated chunks to find player activity. +- **Auto Elytra**: A fully automated feature that manages elytra flight, ensuring continuous and efficient exploration. +- **Chunk Trail Following Algorithm**: A sophisticated algorithm that automatically follows trails of newly generated chunks to locate player activity and bases. - **Stuck Detector**: A utility to detect if the player character is stuck, which can be useful during automated exploration. - **Customizable Commands**: - `.stashhunter`: The main command to configure the Stash-Hunter module. diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index fb9f101..45282ee 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -45,7 +45,7 @@ These settings control the behavior of the new chunk detection and trail-followi | `netherOldChunksDetector` | Detects old chunks in the Nether based on missing new blocks. | true | | `endOldChunksDetector` | Detects old chunks in the End based on biome types. | true | | `Chunk Detection Mode` | The mode for detecting new chunks. `BlockExploitMode` is recommended for some servers. | Normal | -| `dynamic-trail-detection` | Enables the automatic following of new chunk trails. | true | +| `chunk-trail-following` | When enabled, the Auto Elytra feature will automatically follow trails of new chunks, creating a fully automated exploration system. | true | | `LiquidExploit` | Estimates new chunks based on flowing liquids. | false | | `BlockUpdateExploit` | Estimates new chunks based on block updates. | false | | `RemoveOnModuleDisabled` | Clears cached chunk data when the module is disabled. | true | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 44874fd..7964064 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -8,7 +8,7 @@ The `StashHunterModule` is the core of this addon. It is a highly configurable m ### Key Features: -- **Automated Elytra Flight**: The module uses `ElytraController` to fly automatically over a specified area, scanning for valuable blocks. +- **Auto Elytra**: A fully automated feature that manages elytra flight for continuous and efficient exploration. It handles takeoff, landing, and maintaining altitude, allowing for seamless travel across vast distances without manual intervention. - **Configurable Scanning**: You can configure various parameters for scanning, such as: - `scan-radius`: The radius around the player to scan for blocks. - `block-detection-threshold`: The minimum number of valuable blocks to trigger a stash detection. @@ -27,7 +27,7 @@ The `StashHunterModule` is the core of this addon. It is a highly configurable m ## NewerNewChunks Module -The `NewerNewChunks` module is a powerful tool for identifying newly generated chunks, which can indicate recent player activity. This module is essential for the "dynamic chunk trail searching" feature. +The `NewerNewChunks` module is a powerful tool for identifying newly generated chunks, which can indicate recent player activity. This module is essential for the "chunktrail following algorithm" feature. ### Key Features: @@ -36,7 +36,7 @@ The `NewerNewChunks` module is a powerful tool for identifying newly generated c - **LiquidExploit**: Identifies new chunks by looking for flowing liquids, which often indicates recent world generation. - **BlockUpdateExploit**: Detects block updates that can signify new chunk generation. - **Old Chunk Detection**: The module can also identify chunks generated in older versions of Minecraft, helping to distinguish between old and new areas. -- **Dynamic Trail Following**: When enabled, the `ElytraController` will automatically follow trails of new chunks. This is a powerful way to find bases and other points of interest. +- **Chunk Trail Following Algorithm**: A sophisticated algorithm that, when enabled, directs the `Auto Elytra` feature to automatically follow trails of new chunks. This creates a fully automated system for discovering player activity, bases, and other points of interest by tracking the paths of recent world generation. - **Configurable Rendering**: You can customize the color and rendering style of different types of chunks (new, old, etc.) to make them easily visible. ## Stuck Detector Module diff --git a/docs/USAGE.md b/docs/USAGE.md index f21eae3..4251e94 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -9,7 +9,7 @@ The `.stashhunter` command (which can be shortened to `.sh`) is the main command ### Sub-commands - `.stashhunter start [stripWidth]` - - **Description**: Starts the automated scanning process in a defined rectangular area. + - **Description**: Starts the automated scanning process in a defined rectangular area. This mode is ideal for systematically searching a specific region. - **Arguments**: - ` `: The coordinates of the starting corner of the area. - ` `: The coordinates of the opposite corner of the area. @@ -24,8 +24,12 @@ The `.stashhunter` command (which can be shortened to `.sh`) is the main command ``` This command will scan a 20,000 x 20,000 area around you, with a strip width of 200 blocks. +- `.stashhunter trail` + - **Description**: Activates the fully automated chunk trail following mode. In this mode, the addon will automatically detect and follow trails of newly generated chunks, allowing for autonomous exploration to find player activity. + - **Note**: This command does not require coordinates, as it dynamically follows chunk trails. + - `.stashhunter stop` - - **Description**: Stops the current scanning operation. + - **Description**: Stops the current scanning or trail-following operation. - `.stashhunter status` - **Description**: Shows the current status of the Stash-Hunter, including whether it's active, the current progress (waypoints), and the current target coordinates. diff --git a/gradle.properties b/gradle.properties index 317fd43..957e135 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ yarn_mappings=1.21.8+build.1 loader_version=0.16.14 # Mod Properties -mod_version=0.1.4 +mod_version=0.1.5 maven_group=com.example archives_base_name=stash-hunter From f4aaf3ab72f65fa02aaac24a28321bbde24785e7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 15:30:20 +0000 Subject: [PATCH 11/12] docs: consolidate all documentation into README.md This commit combines the documentation from the `docs/` directory into the main `README.md` file to create a single source of truth for the project. - The contents of `docs/FEATURES.md`, `docs/USAGE.md`, and `docs/CONFIGURATION.md` have been merged into `README.md`. - The `docs/` directory has been removed. --- README.md | 185 +++++++++++++++++++++++++++++++++++++++--- docs/CONFIGURATION.md | 70 ---------------- docs/FEATURES.md | 62 -------------- docs/USAGE.md | 46 ----------- 4 files changed, 175 insertions(+), 188 deletions(-) delete mode 100644 docs/CONFIGURATION.md delete mode 100644 docs/FEATURES.md delete mode 100644 docs/USAGE.md diff --git a/README.md b/README.md index 2e6f26f..2c7d904 100644 --- a/README.md +++ b/README.md @@ -23,19 +23,184 @@ A Meteor client addon for finding stashes on anarchy servers. This mod is design ## Usage -Once in-game, you can enable the `StashHunter` module through the Meteor Client GUI. Use the following commands to manage the addon: +This document provides detailed instructions on how to use the commands available in the Stash-Hunter addon. -- `.stashhunter help`: Displays help for the stashhunter command. +### Main Command: `.stashhunter` -The HUD can be enabled and configured from the Meteor Client HUD settings. +The `.stashhunter` command (which can be shortened to `.sh`) is the main command for controlling the automated flight and scanning system. -## Documentation +#### Sub-commands -For more detailed information, please see the following documents: - -- [**Features**](./docs/FEATURES.md): A detailed overview of all features. -- [**Usage**](./docs/USAGE.md): In-depth instructions for all commands. -- [**Configuration**](./docs/CONFIGURATION.md): A guide to all available settings. +- `.stashhunter start [stripWidth]` + - **Description**: Starts the automated scanning process in a defined rectangular area. This mode is ideal for systematically searching a specific region. + - **Arguments**: + - ` `: The coordinates of the starting corner of the area. + - ` `: The coordinates of the opposite corner of the area. + - `[stripWidth]` (optional): The width of the strips the bot will fly in. Defaults to `128`. Must be between 50 and 500. + - **Coordinate Formats**: + - **Absolute**: Use standard coordinates (e.g., `10000 5000`). + - **Relative**: Use `~` to represent your current position (e.g., `~ ~` for your current X and Z). + - **Relative with Offset**: Use `~` followed by a number to specify an offset from your current position (e.g., `~-5000` for 5000 blocks in the negative direction from your current location). + - **Example**: + ``` + .sh start ~-10000 ~-10000 ~10000 ~10000 200 + ``` + This command will scan a 20,000 x 20,000 area around you, with a strip width of 200 blocks. + +- `.stashhunter trail` + - **Description**: Activates the fully automated chunk trail following mode. In this mode, the addon will automatically detect and follow trails of newly generated chunks, allowing for autonomous exploration to find player activity. + - **Note**: This command does not require coordinates, as it dynamically follows chunk trails. + +- `.stashhunter stop` + - **Description**: Stops the current scanning or trail-following operation. + +- `.stashhunter status` + - **Description**: Shows the current status of the Stash-Hunter, including whether it's active, the current progress (waypoints), and the current target coordinates. + +- `.stashhunter help` + - **Description**: Displays a help message with a summary of all commands and examples. + +### Utility Commands + +- `.clear-stashes` + - **Description**: Clears the internal list of stashes that have already been found and reported. This is useful if you want the mod to notify you about a previously found stash again. + +- `.clear-players` + - **Description**: Clears the list of players that have been recently detected. This will allow the mod to send a new notification for a player that is still in the area. + +## Configuration + +This document provides a detailed overview of all the settings available for customization in the Stash-Hunter addon. + +### Stash-Hunter Module + +These settings control the core functionality of the stash finding process. + +| Setting | Description | Default Value | +| ------------------------------ | ------------------------------------------------------------------------------------------------------- | ------------- | +| `discord-webhook-url` | The Discord webhook URL to send notifications to. | (empty) | +| `block-detection-threshold` | The number of valuable blocks to find before a stash is detected. | 10 | +| `scan-radius` | The radius (in blocks) to scan for valuable blocks around the player. | 64 | +| `storage-only-mode` | If enabled, only searches for storage containers (chests, shulkers). Faster than a full block scan. | true | +| `max-volume-threshold` | The maximum volume of a block cluster to be considered a stash. Helps filter out large natural structures. | 5000 | +| `filter-natural-structures` | If enabled, attempts to filter out likely natural structures like dungeons and mineshafts. | true | +| `min-density-threshold` | The minimum density (blocks/volume) required for a cluster to be considered a potential stash. | 0.002 | +| `notification-density-threshold` | The minimum density required to send a Discord notification. Set to 0 to be notified for all finds. | 0.005 | +| `max-cluster-distance` | The maximum distance between two blocks for them to be considered part of the same cluster. | 30 | +| `flight-altitude` | The altitude (Y-level) the bot will fly at during scanning. | 320 | +| `scan-interval` | The interval in ticks between each scan for blocks (20 ticks = 1 second). | 40 | +| `player-detection` | Whether to notify when another player is detected nearby. | true | +| `notify-on-death` | Whether to send a Discord notification if you die. | true | +| `notify-on-completion` | Whether to send a Discord notification when the scanning of a defined area is complete. | true | + +### Stuck Detector Module + +These settings control the behavior of the elytra rubber-band detection system. + +| Setting | Description | Default Value | +| --------------------- | ------------------------------------------------------------------------------------ | ------------- | +| `discord-webhook-url` | The Discord webhook URL to send stuck notifications to. Can be the same or different from the main one. | (empty) | +| `detection-threshold` | The time in seconds the player needs to be motionless before being considered stuck. | 3 | +| `auto-fix` | If enabled, automatically tries to fix the rubber-banding by toggling `ElytraFly`. | true | + +### NewerNewChunks Module + +These settings control the behavior of the new chunk detection and trail-following system. + +| Setting | Description | Default Value | +| ------------------------------ | ------------------------------------------------------------------------------------------------------- | ------------- | +| `PaletteExploit` | (1.18+ only) Detects new chunks by scanning chunk palettes. | true | +| `beingUpdatedDetector` | Marks chunks that are being updated from an older version. Requires `PaletteExploit`. | true | +| `overworldOldChunksDetector` | Detects old chunks in the Overworld based on block types. | true | +| `netherOldChunksDetector` | Detects old chunks in the Nether based on missing new blocks. | true | +| `endOldChunksDetector` | Detects old chunks in the End based on biome types. | true | +| `Chunk Detection Mode` | The mode for detecting new chunks. `BlockExploitMode` is recommended for some servers. | Normal | +| `chunk-trail-following` | When enabled, the Auto Elytra feature will automatically follow trails of new chunks, creating a fully automated exploration system. | true | +| `LiquidExploit` | Estimates new chunks based on flowing liquids. | false | +| `BlockUpdateExploit` | Estimates new chunks based on block updates. | false | +| `RemoveOnModuleDisabled` | Clears cached chunk data when the module is disabled. | true | +| `RemoveOnLeaveWorldOrChangeDimensions` | Clears cached chunk data when changing worlds or dimensions. | true | +| `RemoveOutsideRenderDistance` | Clears cached chunk data for chunks outside your render distance. | false | +| `SaveChunkData` | Saves detected chunk data to a file. | true | +| `LoadChunkData` | Loads chunk data from a file when the module is enabled. | true | +| `AutoReloadChunks` | Automatically reloads chunk data from files periodically. | false | +| `AutoReloadDelayInSeconds` | The delay in seconds for auto-reloading chunk data. | 60 | +| `Render-Distance(Chunks)` | The render distance for chunk overlays. | 64 | +| `render-height` | The Y-level at which to render the chunk overlays. | 0 | +| `shape-mode` | The rendering mode for chunk overlays (Sides, Lines, or Both). | Both | +| `new-chunks-side-color` | The side color for newly generated chunks. | Red (95) | +| `BlockExploitChunks-side-color`| The side color for chunks detected via block updates. | Blue (75) | +| `old-chunks-side-color` | The side color for old chunks. | Green (40) | +| `being-updated-chunks-side-color` | The side color for chunks being updated from an older version. | Yellow (60) | +| `old-version-chunks-side-color`| The side color for chunks from old Minecraft versions. | Yellow (40) | +| `new-chunks-line-color` | The line color for newly generated chunks. | Red (205) | +| `BlockExploitChunks-line-color`| The line color for chunks detected via block updates. | Blue (170) | +| `old-chunks-line-color` | The line color for old chunks. | Green (80) | +| `being-updated-chunks-line-color` | The line color for chunks being updated from an older version. | Yellow (100) | +| `old-version-chunks-line-color`| The line color for chunks from old Minecraft versions. | Yellow (80) | + +## Detailed Features + +This document provides a detailed overview of the features available in the Stash-Hunter addon. + +### Stash-Hunter Module + +The `StashHunterModule` is the core of this addon. It is a highly configurable module that automates the process of finding stashes. + +#### Key Features: + +- **Auto Elytra**: A fully automated feature that manages elytra flight for continuous and efficient exploration. It handles takeoff, landing, and maintaining altitude, allowing for seamless travel across vast distances without manual intervention. +- **Configurable Scanning**: You can configure various parameters for scanning, such as: + - `scan-radius`: The radius around the player to scan for blocks. + - `block-detection-threshold`: The minimum number of valuable blocks to trigger a stash detection. + - `storage-only-mode`: A mode to only search for storage containers like chests and shulker boxes, which is faster than scanning for all valuable blocks. +- **Advanced Filtering**: The module includes advanced filtering to avoid false positives from natural structures: + - `max-volume-threshold`: Sets a maximum volume for a cluster of blocks to be considered a stash. + - `min-density-threshold`: Sets a minimum density (blocks/volume) for a cluster to be considered a stash. + - `filter-natural-structures`: An option to enable or disable the filtering of natural structures. +- **Player Detection**: The module can detect other players within a certain range and send a notification. +- **Discord Integration**: The module can send detailed notifications to a Discord webhook for: + - Found stashes, with coordinates, density, and a list of found containers. + - Detected players. + - Player death events. + - Completion of a scanning mission. +- **Meteor Waypoints**: When a stash is found, a waypoint is automatically created in Meteor Client. + +### NewerNewChunks Module + +The `NewerNewChunks` module is a powerful tool for identifying newly generated chunks, which can indicate recent player activity. This module is essential for the "chunktrail following algorithm" feature. + +#### Key Features: + +- **Multiple Detection Methods**: The module uses several techniques to detect new chunks: + - **PaletteExploit**: (1.18+ only) Detects new chunks by analyzing the structure of chunk data. This is the most reliable method on modern servers. + - **LiquidExploit**: Identifies new chunks by looking for flowing liquids, which often indicates recent world generation. + - **BlockUpdateExploit**: Detects block updates that can signify new chunk generation. +- **Old Chunk Detection**: The module can also identify chunks generated in older versions of Minecraft, helping to distinguish between old and new areas. +- **Chunk Trail Following Algorithm**: A sophisticated algorithm that, when enabled, directs the `Auto Elytra` feature to automatically follow trails of new chunks. This creates a fully automated system for discovering player activity, bases, and other points of interest by tracking the paths of recent world generation. +- **Configurable Rendering**: You can customize the color and rendering style of different types of chunks (new, old, etc.) to make them easily visible. + +### Stuck Detector Module + +The `StuckDetector` is a utility module designed to handle a common issue with elytra flight on anarchy servers: rubber-banding. + +#### Key Features: + +- **Stuck Detection**: The module monitors the player's movement and detects when they are stuck in an elytra rubber-band loop. +- **Auto-Fix**: When enabled, the module will automatically attempt to fix the rubber-banding by: + 1. Toggling the `ElytraFly` module. + 2. If that fails, it will stop the player's gliding. +- **Discord Notifications**: It can send a notification to a Discord webhook when the player gets stuck, and whether it is attempting an automatic fix. + +### Stash-Hunter HUD + +The `StashHunterHud` provides real-time information about the status of the Stash-Hunter on your screen. + +#### HUD Elements: + +- **Status**: Displays the current status of the `ElytraController` (e.g., "Active", "Idle", "Completed"). The color of the text changes based on the status. +- **Target Information**: When active, it shows the coordinates of the current target and the distance to it. +- **Progress**: Displays the progress of the current scanning mission in the format of `current waypoint / total waypoints`. ## Building @@ -70,4 +235,4 @@ This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) f ## Credits -- **omtoi**: Original author. +- **omtoi**: Original author. \ No newline at end of file diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md deleted file mode 100644 index 45282ee..0000000 --- a/docs/CONFIGURATION.md +++ /dev/null @@ -1,70 +0,0 @@ -# Configuration - -This document provides a detailed overview of all the settings available for customization in the Stash-Hunter addon. - -## Stash-Hunter Module - -These settings control the core functionality of the stash finding process. - -| Setting | Description | Default Value | -| ------------------------------ | ------------------------------------------------------------------------------------------------------- | ------------- | -| `discord-webhook-url` | The Discord webhook URL to send notifications to. | (empty) | -| `block-detection-threshold` | The number of valuable blocks to find before a stash is detected. | 10 | -| `scan-radius` | The radius (in blocks) to scan for valuable blocks around the player. | 64 | -| `storage-only-mode` | If enabled, only searches for storage containers (chests, shulkers). Faster than a full block scan. | true | -| `max-volume-threshold` | The maximum volume of a block cluster to be considered a stash. Helps filter out large natural structures. | 5000 | -| `filter-natural-structures` | If enabled, attempts to filter out likely natural structures like dungeons and mineshafts. | true | -| `min-density-threshold` | The minimum density (blocks/volume) required for a cluster to be considered a potential stash. | 0.002 | -| `notification-density-threshold` | The minimum density required to send a Discord notification. Set to 0 to be notified for all finds. | 0.005 | -| `max-cluster-distance` | The maximum distance between two blocks for them to be considered part of the same cluster. | 30 | -| `flight-altitude` | The altitude (Y-level) the bot will fly at during scanning. | 320 | -| `scan-interval` | The interval in ticks between each scan for blocks (20 ticks = 1 second). | 40 | -| `player-detection` | Whether to notify when another player is detected nearby. | true | -| `notify-on-death` | Whether to send a Discord notification if you die. | true | -| `notify-on-completion` | Whether to send a Discord notification when the scanning of a defined area is complete. | true | - -## Stuck Detector Module - -These settings control the behavior of the elytra rubber-band detection system. - -| Setting | Description | Default Value | -| --------------------- | ------------------------------------------------------------------------------------ | ------------- | -| `discord-webhook-url` | The Discord webhook URL to send stuck notifications to. Can be the same or different from the main one. | (empty) | -| `detection-threshold` | The time in seconds the player needs to be motionless before being considered stuck. | 3 | -| `auto-fix` | If enabled, automatically tries to fix the rubber-banding by toggling `ElytraFly`. | true | - -## NewerNewChunks Module - -These settings control the behavior of the new chunk detection and trail-following system. - -| Setting | Description | Default Value | -| ------------------------------ | ------------------------------------------------------------------------------------------------------- | ------------- | -| `PaletteExploit` | (1.18+ only) Detects new chunks by scanning chunk palettes. | true | -| `beingUpdatedDetector` | Marks chunks that are being updated from an older version. Requires `PaletteExploit`. | true | -| `overworldOldChunksDetector` | Detects old chunks in the Overworld based on block types. | true | -| `netherOldChunksDetector` | Detects old chunks in the Nether based on missing new blocks. | true | -| `endOldChunksDetector` | Detects old chunks in the End based on biome types. | true | -| `Chunk Detection Mode` | The mode for detecting new chunks. `BlockExploitMode` is recommended for some servers. | Normal | -| `chunk-trail-following` | When enabled, the Auto Elytra feature will automatically follow trails of new chunks, creating a fully automated exploration system. | true | -| `LiquidExploit` | Estimates new chunks based on flowing liquids. | false | -| `BlockUpdateExploit` | Estimates new chunks based on block updates. | false | -| `RemoveOnModuleDisabled` | Clears cached chunk data when the module is disabled. | true | -| `RemoveOnLeaveWorldOrChangeDimensions` | Clears cached chunk data when changing worlds or dimensions. | true | -| `RemoveOutsideRenderDistance` | Clears cached chunk data for chunks outside your render distance. | false | -| `SaveChunkData` | Saves detected chunk data to a file. | true | -| `LoadChunkData` | Loads chunk data from a file when the module is enabled. | true | -| `AutoReloadChunks` | Automatically reloads chunk data from files periodically. | false | -| `AutoReloadDelayInSeconds` | The delay in seconds for auto-reloading chunk data. | 60 | -| `Render-Distance(Chunks)` | The render distance for chunk overlays. | 64 | -| `render-height` | The Y-level at which to render the chunk overlays. | 0 | -| `shape-mode` | The rendering mode for chunk overlays (Sides, Lines, or Both). | Both | -| `new-chunks-side-color` | The side color for newly generated chunks. | Red (95) | -| `BlockExploitChunks-side-color`| The side color for chunks detected via block updates. | Blue (75) | -| `old-chunks-side-color` | The side color for old chunks. | Green (40) | -| `being-updated-chunks-side-color` | The side color for chunks being updated from an older version. | Yellow (60) | -| `old-version-chunks-side-color`| The side color for chunks from old Minecraft versions. | Yellow (40) | -| `new-chunks-line-color` | The line color for newly generated chunks. | Red (205) | -| `BlockExploitChunks-line-color`| The line color for chunks detected via block updates. | Blue (170) | -| `old-chunks-line-color` | The line color for old chunks. | Green (80) | -| `being-updated-chunks-line-color` | The line color for chunks being updated from an older version. | Yellow (100) | -| `old-version-chunks-line-color`| The line color for chunks from old Minecraft versions. | Yellow (80) | diff --git a/docs/FEATURES.md b/docs/FEATURES.md deleted file mode 100644 index 7964064..0000000 --- a/docs/FEATURES.md +++ /dev/null @@ -1,62 +0,0 @@ -# Features - -This document provides a detailed overview of the features available in the Stash-Hunter addon. - -## Stash-Hunter Module - -The `StashHunterModule` is the core of this addon. It is a highly configurable module that automates the process of finding stashes. - -### Key Features: - -- **Auto Elytra**: A fully automated feature that manages elytra flight for continuous and efficient exploration. It handles takeoff, landing, and maintaining altitude, allowing for seamless travel across vast distances without manual intervention. -- **Configurable Scanning**: You can configure various parameters for scanning, such as: - - `scan-radius`: The radius around the player to scan for blocks. - - `block-detection-threshold`: The minimum number of valuable blocks to trigger a stash detection. - - `storage-only-mode`: A mode to only search for storage containers like chests and shulker boxes, which is faster than scanning for all valuable blocks. -- **Advanced Filtering**: The module includes advanced filtering to avoid false positives from natural structures: - - `max-volume-threshold`: Sets a maximum volume for a cluster of blocks to be considered a stash. - - `min-density-threshold`: Sets a minimum density (blocks/volume) for a cluster to be considered a stash. - - `filter-natural-structures`: An option to enable or disable the filtering of natural structures. -- **Player Detection**: The module can detect other players within a certain range and send a notification. -- **Discord Integration**: The module can send detailed notifications to a Discord webhook for: - - Found stashes, with coordinates, density, and a list of found containers. - - Detected players. - - Player death events. - - Completion of a scanning mission. -- **Meteor Waypoints**: When a stash is found, a waypoint is automatically created in Meteor Client. - -## NewerNewChunks Module - -The `NewerNewChunks` module is a powerful tool for identifying newly generated chunks, which can indicate recent player activity. This module is essential for the "chunktrail following algorithm" feature. - -### Key Features: - -- **Multiple Detection Methods**: The module uses several techniques to detect new chunks: - - **PaletteExploit**: (1.18+ only) Detects new chunks by analyzing the structure of chunk data. This is the most reliable method on modern servers. - - **LiquidExploit**: Identifies new chunks by looking for flowing liquids, which often indicates recent world generation. - - **BlockUpdateExploit**: Detects block updates that can signify new chunk generation. -- **Old Chunk Detection**: The module can also identify chunks generated in older versions of Minecraft, helping to distinguish between old and new areas. -- **Chunk Trail Following Algorithm**: A sophisticated algorithm that, when enabled, directs the `Auto Elytra` feature to automatically follow trails of new chunks. This creates a fully automated system for discovering player activity, bases, and other points of interest by tracking the paths of recent world generation. -- **Configurable Rendering**: You can customize the color and rendering style of different types of chunks (new, old, etc.) to make them easily visible. - -## Stuck Detector Module - -The `StuckDetector` is a utility module designed to handle a common issue with elytra flight on anarchy servers: rubber-banding. - -### Key Features: - -- **Stuck Detection**: The module monitors the player's movement and detects when they are stuck in an elytra rubber-band loop. -- **Auto-Fix**: When enabled, the module will automatically attempt to fix the rubber-banding by: - 1. Toggling the `ElytraFly` module. - 2. If that fails, it will stop the player's gliding. -- **Discord Notifications**: It can send a notification to a Discord webhook when the player gets stuck, and whether it is attempting an automatic fix. - -## Stash-Hunter HUD - -The `StashHunterHud` provides real-time information about the status of the Stash-Hunter on your screen. - -### HUD Elements: - -- **Status**: Displays the current status of the `ElytraController` (e.g., "Active", "Idle", "Completed"). The color of the text changes based on the status. -- **Target Information**: When active, it shows the coordinates of the current target and the distance to it. -- **Progress**: Displays the progress of the current scanning mission in the format of `current waypoint / total waypoints`. diff --git a/docs/USAGE.md b/docs/USAGE.md deleted file mode 100644 index 4251e94..0000000 --- a/docs/USAGE.md +++ /dev/null @@ -1,46 +0,0 @@ -# Usage - -This document provides detailed instructions on how to use the commands available in the Stash-Hunter addon. - -## Main Command: `.stashhunter` - -The `.stashhunter` command (which can be shortened to `.sh`) is the main command for controlling the automated flight and scanning system. - -### Sub-commands - -- `.stashhunter start [stripWidth]` - - **Description**: Starts the automated scanning process in a defined rectangular area. This mode is ideal for systematically searching a specific region. - - **Arguments**: - - ` `: The coordinates of the starting corner of the area. - - ` `: The coordinates of the opposite corner of the area. - - `[stripWidth]` (optional): The width of the strips the bot will fly in. Defaults to `128`. Must be between 50 and 500. - - **Coordinate Formats**: - - **Absolute**: Use standard coordinates (e.g., `10000 5000`). - - **Relative**: Use `~` to represent your current position (e.g., `~ ~` for your current X and Z). - - **Relative with Offset**: Use `~` followed by a number to specify an offset from your current position (e.g., `~-5000` for 5000 blocks in the negative direction from your current location). - - **Example**: - ``` - .sh start ~-10000 ~-10000 ~10000 ~10000 200 - ``` - This command will scan a 20,000 x 20,000 area around you, with a strip width of 200 blocks. - -- `.stashhunter trail` - - **Description**: Activates the fully automated chunk trail following mode. In this mode, the addon will automatically detect and follow trails of newly generated chunks, allowing for autonomous exploration to find player activity. - - **Note**: This command does not require coordinates, as it dynamically follows chunk trails. - -- `.stashhunter stop` - - **Description**: Stops the current scanning or trail-following operation. - -- `.stashhunter status` - - **Description**: Shows the current status of the Stash-Hunter, including whether it's active, the current progress (waypoints), and the current target coordinates. - -- `.stashhunter help` - - **Description**: Displays a help message with a summary of all commands and examples. - -## Utility Commands - -- `.clear-stashes` - - **Description**: Clears the internal list of stashes that have already been found and reported. This is useful if you want the mod to notify you about a previously found stash again. - -- `.clear-players` - - **Description**: Clears the list of players that have been recently detected. This will allow the mod to send a new notification for a player that is still in the area. From e84e022131375eee5fafd9464a3bd48f0168db8c Mon Sep 17 00:00:00 2001 From: omtoi101 <83868916+omtoi101@users.noreply.github.com> Date: Fri, 26 Sep 2025 01:31:31 +1000 Subject: [PATCH 12/12] Delete updates for hacks directory --- updates for hacks/auto_elytra_repair.java | 584 ------------------ updates for hacks/command_updates.java | 64 -- updates for hacks/config_updates.java | 44 -- updates for hacks/instructions.txt | 58 -- updates for hacks/safe_landing_finder.java | 370 ----------- .../stash_hunter_integration.java | 58 -- updates for hacks/stash_hunter_main.java | 25 - 7 files changed, 1203 deletions(-) delete mode 100644 updates for hacks/auto_elytra_repair.java delete mode 100644 updates for hacks/command_updates.java delete mode 100644 updates for hacks/config_updates.java delete mode 100644 updates for hacks/instructions.txt delete mode 100644 updates for hacks/safe_landing_finder.java delete mode 100644 updates for hacks/stash_hunter_integration.java delete mode 100644 updates for hacks/stash_hunter_main.java diff --git a/updates for hacks/auto_elytra_repair.java b/updates for hacks/auto_elytra_repair.java deleted file mode 100644 index 5477aae..0000000 --- a/updates for hacks/auto_elytra_repair.java +++ /dev/null @@ -1,584 +0,0 @@ -package com.stashhunter.stashhunter.modules; - -import com.stashhunter.stashhunter.StashHunter; -import com.stashhunter.stashhunter.utils.Config; -import com.stashhunter.stashhunter.utils.DiscordEmbed; -import com.stashhunter.stashhunter.utils.DiscordWebhook; -import com.stashhunter.stashhunter.utils.ElytraController; -import com.stashhunter.stashhunter.utils.SafeLandingSpotFinder; -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.misc.AutoTool; -import meteordevelopment.meteorclient.systems.modules.player.AutoExp; -import meteordevelopment.meteorclient.systems.modules.movement.elytrafly.ElytraFly; -import meteordevelopment.orbit.EventHandler; -import net.minecraft.block.Block; -import net.minecraft.block.Blocks; -import net.minecraft.entity.EquipmentSlot; -import net.minecraft.item.ItemStack; -import net.minecraft.item.Items; -import net.minecraft.screen.slot.SlotActionType; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; - -import java.util.ArrayList; -import java.util.List; - -public class AutoElytraRepair extends Module { - private final SettingGroup sgGeneral = this.settings.getDefaultGroup(); - - private final Setting repairThreshold = sgGeneral.add(new IntSetting.Builder() - .name("repair-threshold") - .description("Durability threshold to trigger repair (remaining durability).") - .defaultValue(50) - .min(1) - .sliderMax(100) - .build() - ); - - private final Setting landingRadius = sgGeneral.add(new IntSetting.Builder() - .name("landing-radius") - .description("Radius to search for safe landing spots.") - .defaultValue(64) - .min(16) - .sliderMax(128) - .build() - ); - - private final Setting notifyRepairs = sgGeneral.add(new BoolSetting.Builder() - .name("notify-repairs") - .description("Send Discord notifications during repair operations.") - .defaultValue(true) - .build() - ); - - private final Setting repairTimeout = sgGeneral.add(new IntSetting.Builder() - .name("repair-timeout") - .description("Maximum time to spend repairing in seconds.") - .defaultValue(300) - .min(60) - .sliderMax(600) - .build() - ); - - // Repair state - private RepairState currentState = RepairState.MONITORING; - private BlockPos targetLandingSpot = null; - private Vec3d resumePosition = null; - private long repairStartTime = 0; - private int currentRepairSlot = 0; - private List elytraSlots = new ArrayList<>(); - private boolean wasStashHunterActive = false; - private int landingAttempts = 0; - private static final int MAX_LANDING_ATTEMPTS = 3; - - private enum RepairState { - MONITORING, // Normal operation, checking elytra durability - FINDING_LANDING, // Looking for safe landing spot - DESCENDING, // Flying to landing spot - LANDING, // Final landing approach - REPAIRING, // On ground, cycling through elytras for repair - RESUMING, // Taking off and resuming flight - EMERGENCY_DISCONNECT // Critical failure, preparing to disconnect - } - - public AutoElytraRepair() { - super(StashHunter.CATEGORY, "auto-elytra-repair", "Automatically repairs elytras when they get low on durability."); - } - - @Override - public void onActivate() { - currentState = RepairState.MONITORING; - resetRepairState(); - } - - @Override - public void onDeactivate() { - if (currentState != RepairState.MONITORING) { - // Emergency cleanup - resumeNormalOperation(); - } - } - - @EventHandler - private void onTick(TickEvent.Post event) { - if (mc.player == null || mc.world == null) { - return; - } - - switch (currentState) { - case MONITORING: - handleMonitoring(); - break; - case FINDING_LANDING: - handleFindingLanding(); - break; - case DESCENDING: - handleDescending(); - break; - case LANDING: - handleLanding(); - break; - case REPAIRING: - handleRepairing(); - break; - case RESUMING: - handleResuming(); - break; - case EMERGENCY_DISCONNECT: - handleEmergencyDisconnect(); - break; - } - } - - private void handleMonitoring() { - if (!ElytraController.isActive()) { - return; // Only monitor when stash hunter is active - } - - ItemStack chestSlot = mc.player.getEquippedStack(EquipmentSlot.CHEST); - if (chestSlot.getItem() != Items.ELYTRA) { - return; // No elytra equipped - } - - // Check current elytra durability - if (needsRepair(chestSlot)) { - // Find all elytras in inventory - findElytraSlots(); - - if (elytraSlots.isEmpty()) { - // No elytras to repair with - emergency disconnect - error("No elytras found in inventory for repair!"); - initiateEmergencyDisconnect("No backup elytras available"); - return; - } - - // Check if any elytras can be repaired - if (!hasRepairableElytras()) { - error("All elytras are too damaged to repair!"); - initiateEmergencyDisconnect("All elytras beyond repair threshold"); - return; - } - - info("Elytra needs repair (durability: " + (chestSlot.getMaxDamage() - chestSlot.getDamage()) + - "/" + chestSlot.getMaxDamage() + "). Initiating repair sequence."); - - if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { - DiscordEmbed embed = new DiscordEmbed( - "Elytra Repair Initiated", - "Current elytra durability low. Starting repair sequence.\n" + - "Position: " + mc.player.getBlockPos().toShortString() + "\n" + - "Available elytras: " + elytraSlots.size(), - 0xFFAA00 - ); - DiscordWebhook.sendMessage("", embed); - } - - initiateRepairSequence(); - } - } - - private void handleFindingLanding() { - Vec3d playerPos = mc.player.getPos(); - - // Find safe landing spot - BlockPos landingSpot = SafeLandingSpotFinder.findSafeLandingSpot( - playerPos, landingRadius.get(), mc.world - ); - - if (landingSpot != null) { - targetLandingSpot = landingSpot; - resumePosition = playerPos; // Remember where we were - currentState = RepairState.DESCENDING; - info("Found safe landing spot at " + landingSpot.toShortString()); - } else { - landingAttempts++; - if (landingAttempts >= MAX_LANDING_ATTEMPTS) { - error("Could not find safe landing spot after " + MAX_LANDING_ATTEMPTS + " attempts!"); - initiateEmergencyDisconnect("No safe landing spot found"); - return; - } - - // Try expanding search radius - int expandedRadius = landingRadius.get() * (landingAttempts + 1); - info("Expanding landing search radius to " + expandedRadius + " blocks..."); - - // Continue searching with expanded radius next tick - } - } - - private void handleDescending() { - if (targetLandingSpot == null) { - currentState = RepairState.FINDING_LANDING; - return; - } - - Vec3d playerPos = mc.player.getPos(); - double horizontalDistance = Math.sqrt( - Math.pow(targetLandingSpot.getX() - playerPos.x, 2) + - Math.pow(targetLandingSpot.getZ() - playerPos.z, 2) - ); - - // Navigate to landing spot - if (horizontalDistance > 5.0) { - // Fly toward landing spot - flyToPosition(new Vec3d(targetLandingSpot.getX(), playerPos.y, targetLandingSpot.getZ())); - } else { - // Close enough horizontally, start final descent - currentState = RepairState.LANDING; - info("Beginning final landing approach..."); - } - } - - private void handleLanding() { - if (targetLandingSpot == null) { - currentState = RepairState.FINDING_LANDING; - return; - } - - Vec3d playerPos = mc.player.getPos(); - double groundDistance = playerPos.y - (targetLandingSpot.getY() + 1); - - if (mc.player.isOnGround()) { - // Successfully landed - info("Successfully landed at " + mc.player.getBlockPos().toShortString()); - currentState = RepairState.REPAIRING; - repairStartTime = System.currentTimeMillis(); - currentRepairSlot = 0; - - // Enable AutoExp module - AutoExp autoExp = Modules.get().get(AutoExp.class); - if (autoExp != null && !autoExp.isActive()) { - autoExp.toggle(); - info("Enabled AutoExp for repair operations"); - } - } else if (groundDistance > 10) { - // Continue descending - Vec3d landingPos = new Vec3d(targetLandingSpot.getX(), targetLandingSpot.getY() + 2, targetLandingSpot.getZ()); - flyToPosition(landingPos); - - // Stop gliding when close to ground for safer landing - if (groundDistance < 5 && mc.player.isGliding()) { - mc.player.stopGliding(); - } - } else { - // Very close to ground, let gravity handle it - if (mc.player.isGliding()) { - mc.player.stopGliding(); - } - } - } - - private void handleRepairing() { - // Check for timeout - if (System.currentTimeMillis() - repairStartTime > repairTimeout.get() * 1000L) { - warning("Repair timeout reached. Resuming flight with current elytra."); - currentState = RepairState.RESUMING; - return; - } - - if (currentRepairSlot >= elytraSlots.size()) { - // Finished cycling through all elytras - info("Completed repair cycle. Selecting best elytra and resuming flight."); - selectBestElytra(); - currentState = RepairState.RESUMING; - return; - } - - // Get current elytra in repair slot - int slotIndex = elytraSlots.get(currentRepairSlot); - ItemStack elytra = mc.player.getInventory().getStack(slotIndex); - - if (elytra.getItem() != Items.ELYTRA) { - // Elytra was moved or consumed, skip this slot - currentRepairSlot++; - return; - } - - // Move elytra to offhand for repair - if (!isElytraInOffhand(elytra)) { - moveElytraToOffhand(slotIndex); - info("Moved elytra to offhand for repair (slot " + slotIndex + ")"); - } - - // Check if this elytra is fully repaired or good enough - if (!needsRepair(elytra)) { - info("Elytra in slot " + slotIndex + " is sufficiently repaired"); - currentRepairSlot++; - - // Wait a bit before moving to next elytra - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } - - private void handleResuming() { - // Disable AutoExp - AutoExp autoExp = Modules.get().get(AutoExp.class); - if (autoExp != null && autoExp.isActive()) { - autoExp.toggle(); - info("Disabled AutoExp after repair completion"); - } - - // Ensure best elytra is equipped - selectBestElytra(); - - // Take off and resume flight - if (mc.player.isOnGround()) { - // Jump to start takeoff - mc.options.jumpKey.setPressed(true); - - // After a brief delay, start elytra flight - new Thread(() -> { - try { - Thread.sleep(500); // Wait for jump - mc.options.jumpKey.setPressed(false); - - Thread.sleep(1000); // Wait to gain some altitude - - if (mc.player.getEquippedStack(EquipmentSlot.CHEST).getItem() == Items.ELYTRA) { - mc.player.startGliding(); - info("Restarted elytra gliding"); - } - - Thread.sleep(2000); // Wait for stable flight - - // Re-enable ElytraFly module - ElytraFly elytraFly = Modules.get().get(ElytraFly.class); - if (elytraFly != null && !elytraFly.isActive()) { - elytraFly.toggle(); - info("Re-enabled ElytraFly module"); - } - - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - }).start(); - } - - // Resume stash hunter if it was active - if (wasStashHunterActive && !ElytraController.isActive()) { - // ElytraController will resume automatically when conditions are right - info("Repair sequence complete. Stash hunter will resume automatically."); - } - - if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { - DiscordEmbed embed = new DiscordEmbed( - "Elytra Repair Complete", - "Successfully repaired elytras and resumed flight.\n" + - "Position: " + mc.player.getBlockPos().toShortString() + "\n" + - "Status: Resuming stash hunting operations", - 0x00FF00 - ); - DiscordWebhook.sendMessage("", embed); - } - - resetRepairState(); - currentState = RepairState.MONITORING; - } - - private void handleEmergencyDisconnect() { - error("Emergency disconnect initiated. Saving state and disconnecting..."); - - // Save current trip state - if (ElytraController.isActive()) { - ElytraController.pause(); - } - - if (notifyRepairs.get() && !Config.discordWebhookUrl.isEmpty()) { - DiscordEmbed embed = new DiscordEmbed( - "Emergency Disconnect", - "Critical elytra repair failure. Bot is disconnecting for safety.\n" + - "Position: " + (mc.player != null ? mc.player.getBlockPos().toShortString() : "Unknown") + "\n" + - "Reason: Unable to repair elytras safely", - 0xFF0000 - ); - DiscordWebhook.sendMessage("@everyone", embed); - } - - // Disconnect from server - if (mc.getNetworkHandler() != null) { - mc.getNetworkHandler().getConnection().disconnect( - net.minecraft.text.Text.of("Emergency disconnect: Elytra repair failed") - ); - } - - this.toggle(); // Disable the module - } - - private void initiateRepairSequence() { - wasStashHunterActive = ElytraController.isActive(); - - // Pause stash hunter - if (ElytraController.isActive()) { - ElytraController.pause(); - info("Paused stash hunter for elytra repair"); - } - - currentState = RepairState.FINDING_LANDING; - landingAttempts = 0; - } - - private void initiateEmergencyDisconnect(String reason) { - error("Initiating emergency disconnect: " + reason); - currentState = RepairState.EMERGENCY_DISCONNECT; - } - - private boolean needsRepair(ItemStack elytra) { - if (elytra.getItem() != Items.ELYTRA) return false; - int remainingDurability = elytra.getMaxDamage() - elytra.getDamage(); - return remainingDurability <= repairThreshold.get(); - } - - private void findElytraSlots() { - elytraSlots.clear(); - - for (int i = 0; i < mc.player.getInventory().size(); i++) { - ItemStack stack = mc.player.getInventory().getStack(i); - if (stack.getItem() == Items.ELYTRA) { - elytraSlots.add(i); - } - } - - info("Found " + elytraSlots.size() + " elytras in inventory"); - } - - private boolean hasRepairableElytras() { - for (int slot : elytraSlots) { - ItemStack elytra = mc.player.getInventory().getStack(slot); - if (elytra.getItem() == Items.ELYTRA) { - int remainingDurability = elytra.getMaxDamage() - elytra.getDamage(); - // Can be repaired if it has at least some durability and can benefit from mending - if (remainingDurability > 10 && elytra.getDamage() > 0) { - return true; - } - } - } - return false; - } - - private boolean isElytraInOffhand(ItemStack targetElytra) { - ItemStack offhand = mc.player.getInventory().getStack(40); // Offhand slot - return offhand.getItem() == Items.ELYTRA && offhand.getDamage() == targetElytra.getDamage(); - } - - private void moveElytraToOffhand(int fromSlot) { - // Click on the elytra in inventory - mc.interactionManager.clickSlot( - mc.player.currentScreenHandler.syncId, - fromSlot < 9 ? fromSlot + 36 : fromSlot, // Convert to screen handler slot - 0, - SlotActionType.PICKUP, - mc.player - ); - - // Click on offhand slot - mc.interactionManager.clickSlot( - mc.player.currentScreenHandler.syncId, - 45, // Offhand slot in screen handler - 0, - SlotActionType.PICKUP, - mc.player - ); - } - - private void selectBestElytra() { - ItemStack bestElytra = null; - int bestSlot = -1; - int bestDurability = 0; - - // Find elytra with highest durability - for (int slot : elytraSlots) { - ItemStack elytra = mc.player.getInventory().getStack(slot); - if (elytra.getItem() == Items.ELYTRA) { - int durability = elytra.getMaxDamage() - elytra.getDamage(); - if (durability > bestDurability) { - bestDurability = durability; - bestElytra = elytra; - bestSlot = slot; - } - } - } - - if (bestElytra != null && bestSlot != -1) { - // Equip the best elytra - equipElytra(bestSlot); - info("Equipped elytra with " + bestDurability + " durability"); - } - } - - private void equipElytra(int slot) { - // Move elytra to chest slot - mc.interactionManager.clickSlot( - mc.player.currentScreenHandler.syncId, - slot < 9 ? slot + 36 : slot, - 0, - SlotActionType.PICKUP, - mc.player - ); - - mc.interactionManager.clickSlot( - mc.player.currentScreenHandler.syncId, - 6, // Chest slot in screen handler - 0, - SlotActionType.PICKUP, - mc.player - ); - } - - private void flyToPosition(Vec3d target) { - if (mc.player == null) return; - - Vec3d playerPos = mc.player.getPos(); - double dx = target.x - playerPos.x; - double dz = target.z - playerPos.z; - double dy = target.y - playerPos.y; - - double horizontalDistance = Math.sqrt(dx * dx + dz * dz); - - if (horizontalDistance > 1.0) { - double yaw = Math.atan2(dz, dx) * 180.0 / Math.PI - 90.0; - double pitch = Math.atan2(dy, horizontalDistance) * 180.0 / Math.PI; - - // Limit pitch for safe flying - pitch = Math.max(-30.0, Math.min(30.0, pitch)); - - mc.player.setYaw((float) yaw); - mc.player.setPitch((float) pitch); - } - } - - private void resetRepairState() { - targetLandingSpot = null; - resumePosition = null; - repairStartTime = 0; - currentRepairSlot = 0; - elytraSlots.clear(); - wasStashHunterActive = false; - landingAttempts = 0; - } - - private void resumeNormalOperation() { - resetRepairState(); - currentState = RepairState.MONITORING; - - // Re-enable ElytraFly if needed - ElytraFly elytraFly = Modules.get().get(ElytraFly.class); - if (elytraFly != null && !elytraFly.isActive() && wasStashHunterActive) { - elytraFly.toggle(); - } - } - - public RepairState getCurrentState() { - return currentState; - } - - public boolean isRepairing() { - return currentState != RepairState.MONITORING; - } -} \ No newline at end of file diff --git a/updates for hacks/command_updates.java b/updates for hacks/command_updates.java deleted file mode 100644 index 00215c3..0000000 --- a/updates for hacks/command_updates.java +++ /dev/null @@ -1,64 +0,0 @@ -// Add these imports to StashHunterCommand.java: -import com.stashhunter.stashhunter.modules.AutoElytraRepair; -import meteordevelopment.meteorclient.systems.modules.Modules; - -// Add this new command to the build() method in StashHunterCommand.java: - -// Repair status command -builder.then(literal("repair") - .executes(context -> { - AutoElytraRepair repairModule = Modules.get().get(AutoElytraRepair.class); - if (repairModule == null) { - error("Auto Elytra Repair module not found."); - return SINGLE_SUCCESS; - } - - if (!repairModule.isActive()) { - info("Auto Elytra Repair: §cDisabled"); - } else if (repairModule.isRepairing()) { - info("Auto Elytra Repair: §eActive - " + repairModule.getCurrentState().toString()); - } else { - info("Auto Elytra Repair: §aEnabled - Monitoring"); - } - - // Show current elytra status - if (MeteorClient.mc.player != null) { - ItemStack chestSlot = MeteorClient.mc.player.getEquippedStack(EquipmentSlot.CHEST); - if (chestSlot.getItem() == Items.ELYTRA) { - int durability = chestSlot.getMaxDamage() - chestSlot.getDamage(); - int maxDurability = chestSlot.getMaxDamage(); - info("Current Elytra: " + durability + "/" + maxDurability + " durability"); - } else { - info("No Elytra currently equipped"); - } - } - - return SINGLE_SUCCESS; - }) - .then(literal("toggle") - .executes(context -> { - AutoElytraRepair repairModule = Modules.get().get(AutoElytraRepair.class); - if (repairModule == null) { - error("Auto Elytra Repair module not found."); - return SINGLE_SUCCESS; - } - - repairModule.toggle(); - info("Auto Elytra Repair: " + (repairModule.isActive() ? "§aEnabled" : "§cDisabled")); - return SINGLE_SUCCESS; - }) - ) -); - -// Update the help command to include repair information: -// In the help command execution, add these lines: - -info("§7/stashhunter repair §f- Show elytra repair status"); -info("§7/stashhunter repair toggle §f- Toggle auto elytra repair"); - -// Add to the detailed help section: -info(""); -info("§eAuto Elytra Repair:"); -info("§7Automatically repairs elytras when durability gets low"); -info("§7Finds safe landing spots and cycles through elytras for mending"); -info("§7Requires AutoExp module to be available in Meteor Client"); \ No newline at end of file diff --git a/updates for hacks/config_updates.java b/updates for hacks/config_updates.java deleted file mode 100644 index 6095e6d..0000000 --- a/updates for hacks/config_updates.java +++ /dev/null @@ -1,44 +0,0 @@ -// Add these new configuration variables to Config.java - -// Auto Elytra Repair settings -public static int autoRepairThreshold = 50; -public static int autoRepairLandingRadius = 64; -public static boolean autoRepairNotifications = true; -public static int autoRepairTimeout = 300; - -// Add these to the load() method: -autoRepairThreshold = getIntProperty("autoRepairThreshold", 50); -autoRepairLandingRadius = getIntProperty("autoRepairLandingRadius", 64); -autoRepairNotifications = getBoolProperty("autoRepairNotifications", true); -autoRepairTimeout = getIntProperty("autoRepairTimeout", 300); - -// Add these to the save() method: -properties.setProperty("autoRepairThreshold", String.valueOf(autoRepairThreshold)); -properties.setProperty("autoRepairLandingRadius", String.valueOf(autoRepairLandingRadius)); -properties.setProperty("autoRepairNotifications", String.valueOf(autoRepairNotifications)); -properties.setProperty("autoRepairTimeout", String.valueOf(autoRepairTimeout)); - -// Add validation in the validate() method: -if (autoRepairThreshold < 1) { - autoRepairThreshold = 1; - changed = true; -} else if (autoRepairThreshold > 100) { - autoRepairThreshold = 100; - changed = true; -} - -if (autoRepairLandingRadius < 16) { - autoRepairLandingRadius = 16; - changed = true; -} else if (autoRepairLandingRadius > 128) { - autoRepairLandingRadius = 128; - changed = true; -} - -if (autoRepairTimeout < 60) { - autoRepairTimeout = 60; - changed = true; -} else if (autoRepairTimeout > 600) { - autoRepairTimeout = 600; - changed = true; -} \ No newline at end of file diff --git a/updates for hacks/instructions.txt b/updates for hacks/instructions.txt deleted file mode 100644 index 0fb8a98..0000000 --- a/updates for hacks/instructions.txt +++ /dev/null @@ -1,58 +0,0 @@ -Installation Instructions -Here's how to integrate the auto-repair plugin into your current Stash Hunter code: -1. New Files to Create -Create these new files in your project: - -src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java (from first artifact) -src/main/java/com/stashhunter/stashhunter/utils/SafeLandingSpotFinder.java (from second artifact) - -2. Existing Files to Update -Update StashHunterModule.java: - -Add the import and integration code from the third artifact -This replaces the existing elytra checking logic with enhanced logic that works with the repair system - -Update StashHunter.java (main class): - -Add the module registration code from the fourth artifact -This registers the new AutoElytraRepair module with Meteor - -Update Config.java: - -Add the configuration variables and validation from the fifth artifact -This adds settings for repair thresholds and timeouts - -Update StashHunterCommand.java: - -Add the new repair command from the sixth artifact -This adds /stashhunter repair commands for monitoring and control - -3. How the Auto-Repair System Works - -Monitoring: Continuously checks elytra durability while stash hunting is active -Safe Landing: When durability drops below threshold, finds the safest landing spot within radius -Landing Process: Safely descends and lands, avoiding hazards like water, lava, or confined spaces -Repair Cycle: Activates AutoExp module and cycles through all elytras in inventory for mending -Resume Flight: Equips the best repaired elytra and resumes stash hunting - -4. Configuration Options -The system adds these settings: - -Repair Threshold: Durability level to trigger repair (default: 50) -Landing Radius: Search radius for safe spots (default: 64 blocks) -Repair Timeout: Maximum repair time (default: 300 seconds) -Discord Notifications: Alert on Discord during repairs (default: true) - -5. Safety Features - -Hazard Avoidance: Won't land in water, lava, or near dangerous blocks -Takeoff Clearance: Ensures sufficient overhead space for safe takeoff -Emergency Disconnect: If no safe landing spot found or all elytras are beyond repair -Progress Persistence: Saves trip progress before landing, resumes after repair - -6. Commands - -/stashhunter repair - Show current repair status -/stashhunter repair toggle - Enable/disable auto-repair system - -The system integrates seamlessly with your existing Stash Hunter and will automatically handle elytra repairs without interrupting the scanning process, making your bot much more autonomous and reliable for long scanning operations. \ No newline at end of file diff --git a/updates for hacks/safe_landing_finder.java b/updates for hacks/safe_landing_finder.java deleted file mode 100644 index a38d8ac..0000000 --- a/updates for hacks/safe_landing_finder.java +++ /dev/null @@ -1,370 +0,0 @@ -package com.stashhunter.stashhunter.utils; - -import net.minecraft.block.Block; -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; -import net.minecraft.client.world.ClientWorld; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; - -import java.util.HashSet; -import java.util.Set; - -public class SafeLandingSpotFinder { - - // Blocks that are safe to land on - private static final Set SAFE_LANDING_BLOCKS = Set.of( - Blocks.GRASS_BLOCK, - Blocks.DIRT, - Blocks.STONE, - Blocks.COBBLESTONE, - Blocks.SAND, - Blocks.GRAVEL, - Blocks.NETHERRACK, - Blocks.END_STONE, - Blocks.OBSIDIAN, - Blocks.DEEPSLATE, - Blocks.ANDESITE, - Blocks.DIORITE, - Blocks.GRANITE, - Blocks.SANDSTONE, - Blocks.RED_SANDSTONE - ); - - // Blocks that are hazardous to land on or near - private static final Set HAZARDOUS_BLOCKS = Set.of( - Blocks.LAVA, - Blocks.WATER, - Blocks.CACTUS, - Blocks.SWEET_BERRY_BUSH, - Blocks.WITHER_ROSE, - Blocks.FIRE, - Blocks.SOUL_FIRE, - Blocks.MAGMA_BLOCK, - Blocks.CAMPFIRE, - Blocks.SOUL_CAMPFIRE - ); - - // Blocks that would cause head bumping when taking off - private static final Set OVERHEAD_HAZARDS = Set.of( - Blocks.STONE, - Blocks.COBBLESTONE, - Blocks.DEEPSLATE, - Blocks.ANDESITE, - Blocks.DIORITE, - Blocks.GRANITE, - Blocks.OBSIDIAN, - Blocks.BEDROCK, - Blocks.NETHERRACK, - Blocks.END_STONE, - Blocks.SANDSTONE, - Blocks.RED_SANDSTONE, - Blocks.DIRT, - Blocks.GRASS_BLOCK - ); - - /** - * Finds a safe landing spot within the specified radius - * @param startPos Current player position - * @param radius Search radius in blocks - * @param world The world to search in - * @return A safe landing position, or null if none found - */ - public static BlockPos findSafeLandingSpot(Vec3d startPos, int radius, ClientWorld world) { - if (world == null) return null; - - BlockPos centerPos = BlockPos.ofFloored(startPos); - - // Start from current position and spiral outward - for (int r = 0; r <= radius; r += 8) { // Check every 8 blocks for performance - for (int x = -r; x <= r; x += 8) { - for (int z = -r; z <= r; z += 8) { - // Only check positions roughly on the circle edge for this radius - double distance = Math.sqrt(x * x + z * z); - if (distance < r - 4 || distance > r + 4) continue; - - BlockPos checkPos = centerPos.add(x, 0, z); - BlockPos safeSpot = findSafeLandingAtColumn(checkPos, world); - - if (safeSpot != null) { - return safeSpot; - } - } - } - } - - return null; // No safe spot found - } - - /** - * Finds a safe landing spot in a vertical column - * @param columnPos The X,Z position to check vertically - * @param world The world to search in - * @return A safe landing position in this column, or null if none found - */ - private static BlockPos findSafeLandingAtColumn(BlockPos columnPos, ClientWorld world) { - // Start from a reasonable height and work down - int maxY = Math.min(world.getHeight() - 1, columnPos.getY() + 50); - int minY = Math.max(world.getBottomY(), columnPos.getY() - 100); - - for (int y = maxY; y >= minY; y--) { - BlockPos landingPos = new BlockPos(columnPos.getX(), y, columnPos.getZ()); - - if (isSafeLandingSpot(landingPos, world)) { - return landingPos; - } - } - - return null; - } - - /** - * Checks if a specific position is safe for landing - * @param pos The position to check - * @param world The world context - * @return true if safe to land here - */ - public static boolean isSafeLandingSpot(BlockPos pos, ClientWorld world) { - try { - // Check the landing block itself - BlockState landingBlock = world.getBlockState(pos); - if (!SAFE_LANDING_BLOCKS.contains(landingBlock.getBlock()) || !landingBlock.isFullCube(world, pos)) { - return false; - } - - // Check that the two blocks above are air (space for player) - BlockPos abovePos1 = pos.up(); - BlockPos abovePos2 = pos.up(2); - - if (!world.getBlockState(abovePos1).isAir() || !world.getBlockState(abovePos2).isAir()) { - return false; - } - - // Check for overhead hazards that would block takeoff (up to 10 blocks above) - for (int i = 3; i <= 10; i++) { - BlockPos overheadPos = pos.up(i); - BlockState overheadBlock = world.getBlockState(overheadPos); - - if (OVERHEAD_HAZARDS.contains(overheadBlock.getBlock()) && overheadBlock.isFullCube(world, overheadPos)) { - return false; // Would hit head during takeoff - } - } - - // Check surrounding area for hazards (3x3 area) - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - BlockPos surroundingPos = pos.add(dx, 0, dz); - BlockState surroundingBlock = world.getBlockState(surroundingPos); - - if (HAZARDOUS_BLOCKS.contains(surroundingBlock.getBlock())) { - return false; - } - - // Also check one block above surrounding area for hanging hazards - BlockPos aboveSurrounding = surroundingPos.up(); - BlockState aboveSurroundingBlock = world.getBlockState(aboveSurrounding); - - if (HAZARDOUS_BLOCKS.contains(aboveSurroundingBlock.getBlock())) { - return false; - } - } - } - - // Check that we're not landing in a confined space (check 5x5 area for walls) - int wallCount = 0; - for (int dx = -2; dx <= 2; dx++) { - for (int dz = -2; dz <= 2; dz++) { - if (dx == 0 && dz == 0) continue; // Skip center position - - BlockPos wallCheckPos = pos.add(dx, 1, dz); // Check at player height - BlockState wallBlock = world.getBlockState(wallCheckPos); - - if (!wallBlock.isAir() && wallBlock.isFullCube(world, wallCheckPos)) { - wallCount++; - } - } - } - - // If more than 60% of surrounding area is walled, it's too confined - if (wallCount > 15) { // 60% of 24 surrounding blocks - return false; - } - - // Additional safety checks for specific biomes/situations - - // Check if we're above void (in End or below bedrock) - if (pos.getY() < world.getBottomY() + 5) { - return false; - } - - // Check if landing spot has solid ground beneath (not floating) - boolean hasGroundSupport = false; - for (int i = 1; i <= 5; i++) { - BlockPos belowPos = pos.down(i); - BlockState belowBlock = world.getBlockState(belowPos); - - if (!belowBlock.isAir() && belowBlock.isFullCube(world, belowPos)) { - hasGroundSupport = true; - break; - } - } - - if (!hasGroundSupport) { - return false; // Floating platform, not safe - } - - return true; // All checks passed - - } catch (Exception e) { - // If we can't check the blocks safely, assume it's not safe - return false; - } - } - - /** - * Evaluates the safety score of a landing spot (higher is better) - * @param pos The position to evaluate - * @param world The world context - * @return Safety score from 0-100, or -1 if not safe at all - */ - public static int evaluateLandingSpotSafety(BlockPos pos, ClientWorld world) { - if (!isSafeLandingSpot(pos, world)) { - return -1; - } - - int safetyScore = 50; // Base score for any safe spot - - try { - // Bonus points for being on natural ground blocks - BlockState landingBlock = world.getBlockState(pos); - if (landingBlock.getBlock() == Blocks.GRASS_BLOCK || - landingBlock.getBlock() == Blocks.STONE || - landingBlock.getBlock() == Blocks.DIRT) { - safetyScore += 10; - } - - // Bonus points for having more open space above - int openSpaceAbove = 0; - for (int i = 3; i <= 15; i++) { - BlockPos abovePos = pos.up(i); - if (world.getBlockState(abovePos).isAir()) { - openSpaceAbove++; - } else { - break; - } - } - safetyScore += Math.min(openSpaceAbove * 2, 20); // Up to 20 bonus points - - // Bonus points for having more open space around - int openSpaceAround = 0; - for (int dx = -3; dx <= 3; dx++) { - for (int dz = -3; dz <= 3; dz++) { - if (dx == 0 && dz == 0) continue; - - BlockPos aroundPos = pos.add(dx, 1, dz); - if (world.getBlockState(aroundPos).isAir()) { - openSpaceAround++; - } - } - } - safetyScore += Math.min(openSpaceAround, 15); // Up to 15 bonus points - - // Penalty for being too high above ground - int distanceToGround = 0; - for (int i = 1; i <= 50; i++) { - BlockPos belowPos = pos.down(i); - BlockState belowBlock = world.getBlockState(belowPos); - - if (!belowBlock.isAir()) { - distanceToGround = i - 1; - break; - } - } - - if (distanceToGround > 10) { - safetyScore -= (distanceToGround - 10) * 2; // Penalty for being too high - } - - return Math.max(0, Math.min(100, safetyScore)); - - } catch (Exception e) { - return 50; // Return base score if evaluation fails - } - } - - /** - * Finds the best landing spot within radius, considering safety scores - * @param startPos Current player position - * @param radius Search radius in blocks - * @param world The world to search in - * @return The best landing position found, or null if none found - */ - public static BlockPos findBestLandingSpot(Vec3d startPos, int radius, ClientWorld world) { - BlockPos bestSpot = null; - int bestScore = -1; - - BlockPos centerPos = BlockPos.ofFloored(startPos); - - // Search in expanding rings for performance - for (int r = 8; r <= radius; r += 8) { - for (int x = -r; x <= r; x += 4) { - for (int z = -r; z <= r; z += 4) { - // Only check positions roughly on the circle edge for this radius - double distance = Math.sqrt(x * x + z * z); - if (distance < r - 4 || distance > r + 4) continue; - - BlockPos checkPos = centerPos.add(x, 0, z); - BlockPos candidate = findSafeLandingAtColumn(checkPos, world); - - if (candidate != null) { - int score = evaluateLandingSpotSafety(candidate, world); - if (score > bestScore) { - bestScore = score; - bestSpot = candidate; - } - - // If we found a really good spot, use it - if (score >= 80) { - return candidate; - } - } - } - } - - // If we found any decent spot in this ring, don't search further - if (bestScore >= 60) { - break; - } - } - - return bestSpot; - } - - /** - * Quick check if the immediate area below is safe for emergency landing - * @param pos Current position - * @param world The world context - * @return true if it's safe to descend here immediately - */ - public static boolean isEmergencyLandingSafe(Vec3d pos, ClientWorld world) { - BlockPos checkPos = BlockPos.ofFloored(pos); - - // Look for ground within reasonable distance below - for (int i = 0; i < 50; i++) { - BlockPos groundPos = checkPos.down(i); - if (isSafeLandingSpot(groundPos, world)) { - return true; - } - - // If we hit a hazardous block, stop checking - BlockState blockState = world.getBlockState(groundPos); - if (HAZARDOUS_BLOCKS.contains(blockState.getBlock())) { - return false; - } - } - - return false; - } -} - \ No newline at end of file diff --git a/updates for hacks/stash_hunter_integration.java b/updates for hacks/stash_hunter_integration.java deleted file mode 100644 index 963109c..0000000 --- a/updates for hacks/stash_hunter_integration.java +++ /dev/null @@ -1,58 +0,0 @@ -// Updates needed for StashHunterModule.java - -// Add this import at the top: -import com.stashhunter.stashhunter.modules.AutoElytraRepair; - -// Add this field with other module references: -private final AutoElytraRepair autoElytraRepair = Modules.get().get(AutoElytraRepair.class); - -// Replace the existing elytra check logic in onTick() method with this enhanced version: - -// Enhanced Elytra check with repair integration -if (ElytraController.isActive()) { - ItemStack chestSlot = mc.player.getEquippedStack(EquipmentSlot.CHEST); - boolean elytraMissing = chestSlot.isEmpty() || - (chestSlot.getItem() == Items.ELYTRA && chestSlot.isDamage() >= chestSlot.getMaxDamage() - 1); - - // Check if auto repair is handling the situation - if (autoElytraRepair != null && autoElytraRepair.isActive() && autoElytraRepair.isRepairing()) { - // Auto repair is active, let it handle elytra management - return; // Skip normal elytra checks - } - - if (elytraMissing) { - wasElytraBroken = true; - elytraBrokenTicks++; - - // Give auto repair system time to activate before panicking - int timeoutTicks = autoElytraRepair != null && autoElytraRepair.isActive() ? 200 : 40; - - if (elytraBrokenTicks > timeoutTicks) { - // Send Discord notification - DiscordEmbed embed = new DiscordEmbed( - "Out of Elytras!", - "The bot has run out of elytras or all elytras are broken beyond repair, and will now disconnect.", - 0xFF0000 - ); - DiscordWebhook.sendMessage("@everyone", embed); - - // Disconnect from server - if (mc.getNetworkHandler() != null) { - mc.getNetworkHandler().getConnection().disconnect(Text.of("Ran out of elytras or all elytras broken.")); - } - - // Stop elytra controller - ElytraController.stop(); - - // Deactivate the module - toggle(); - return; - } - } else { - elytraBrokenTicks = 0; - if (wasElytraBroken) { - wasElytraBroken = false; - reEngagingElytraTicks = 100; // 5 seconds - } - } -} \ No newline at end of file diff --git a/updates for hacks/stash_hunter_main.java b/updates for hacks/stash_hunter_main.java deleted file mode 100644 index 4ad55b4..0000000 --- a/updates for hacks/stash_hunter_main.java +++ /dev/null @@ -1,25 +0,0 @@ -// Updates needed for StashHunter.java main class - -// Add these imports: -import com.stashhunter.stashhunter.modules.AutoElytraRepair; - -// In the onInitializeClient() method, add the new modules: -@Override -public void onInitializeClient() { - LOG.info("Initializing Stash-Hunter"); - - // Load configuration - Config.load(); - Config.validate(); - - // Add modules - Modules.get().add(new StashHunterModule()); - Modules.get().add(new StuckDetector()); - Modules.get().add(new AltitudeLossDetector()); - Modules.get().add(new AutoElytraRepair()); // Add this line - - // Add commands - Commands.get().add(new StashHunterCommand()); - - LOG.info("Stash-Hunter initialized successfully"); -} \ No newline at end of file