diff --git a/README.md b/README.md index 19b80c5..2c7d904 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. @@ -22,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 @@ -69,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/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/docs/CONFIGURATION.md b/docs/CONFIGURATION.md deleted file mode 100644 index fb9f101..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 | -| `dynamic-trail-detection` | Enables the automatic following of new chunk trails. | 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 44874fd..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: - -- **Automated Elytra Flight**: The module uses `ElytraController` to fly automatically over a specified area, scanning for valuable blocks. -- **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 "dynamic chunk trail searching" 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. -- **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. -- **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 f21eae3..0000000 --- a/docs/USAGE.md +++ /dev/null @@ -1,42 +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. - - **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 stop` - - **Description**: Stops the current scanning 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. 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 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..1747322 --- /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 + 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..6245f52 --- /dev/null +++ b/src/main/java/com/stashhunter/stashhunter/modules/AutoElytraRepair.java @@ -0,0 +1,649 @@ +package com.stashhunter.stashhunter.modules; + +import com.stashhunter.stashhunter.StashHunter; +import com.stashhunter.stashhunter.utils.KeyHold; +import com.stashhunter.stashhunter.utils.ElytraController; +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.combat.AutoEXP; +import meteordevelopment.meteorclient.systems.modules.movement.Scaffold; +import meteordevelopment.meteorclient.systems.modules.movement.elytrafly.ElytraFly; +import meteordevelopment.orbit.EventHandler; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; + +import java.lang.reflect.Field; +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 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() + ); + + 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 long repairStartTime = 0; + private int currentRepairSlot = 0; + private List elytraSlots = new ArrayList<>(); + private boolean wasStashHunterActive = false; + private int timer = 0; + 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, + STOPPING_AUTOPILOT, + WAITING_FOR_STOP, + POSITIONING_FOR_REPAIR, + SCAFFOLDING, + REPAIRING, + REPAIRING_IN_PROGRESS, + RESUMING_FLIGHT, + EMERGENCY_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(); + debugLog("AutoElytraRepair activated"); + } + + @Override + public void onDeactivate() { + if (currentState != RepairState.MONITORING) { + resumeNormalOperation(); + } + debugLog("AutoElytraRepair deactivated"); + } + + @EventHandler + private void onTick(TickEvent.Post event) { + if (mc.player == null || mc.world == null) { + return; + } + + switch (currentState) { + case MONITORING: + handleMonitoring(); + break; + case STOPPING_AUTOPILOT: + handleStoppingAutopilot(); + break; + case WAITING_FOR_STOP: + handleWaitingForStop(); + break; + case POSITIONING_FOR_REPAIR: + handlePositioningForRepair(); + break; + case SCAFFOLDING: + handleScaffolding(); + break; + case REPAIRING: + handleRepairing(); + break; + case REPAIRING_IN_PROGRESS: + handleRepairingInProgress(); + break; + case RESUMING_FLIGHT: + handleResumingFlight(); + break; + case EMERGENCY_DISCONNECT: + handleEmergencyDisconnect(); + break; + } + } + + 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 handleStoppingAutopilot() { + ElytraFly elytraFly = Modules.get().get(ElytraFly.class); + if (elytraFly == null) { + error("ElytraFly not found! Aborting repair."); + currentState = RepairState.EMERGENCY_DISCONNECT; + return; + } + + // 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 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; + } + } + + 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; + } + + // 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; + } + + // 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 + } + + // 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; + } + } + + // Now actually enable scaffold and auto exp together + originalScaffoldAirPlace = getModuleSetting(scaffold, "airPlace"); + originalScaffoldAutoSwitch = getModuleSetting(scaffold, "autoSwitch"); + setModuleSetting(scaffold, "airPlace", true); + setModuleSetting(scaffold, "autoSwitch", true); + + if (!scaffold.isActive()) scaffold.toggle(); + + // 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"); + } + + 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; + } + + // 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; + } + + 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(); + + // 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 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; + } + + // 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; + } + + // 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() { + 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."); + currentState = RepairState.RESUMING_FLIGHT; + return; + } + + ItemStack chestElytra = mc.player.getEquippedStack(EquipmentSlot.CHEST); + if (chestElytra.getItem() != Items.ELYTRA) { + warning("No elytra equipped during repair."); + currentState = RepairState.RESUMING_FLIGHT; + return; + } + + if (chestElytra.getDamage() == 0) { + info("Equipped elytra repaired. Resuming flight."); + currentState = RepairState.RESUMING_FLIGHT; + return; + } + + AutoEXP autoExp = Modules.get().get(AutoEXP.class); + if (autoExp != null && !autoExp.isActive()) autoExp.toggle(); + currentState = RepairState.REPAIRING_IN_PROGRESS; + } + + private void handleRepairingInProgress() { + ItemStack currentElytra = mc.player.getEquippedStack(EquipmentSlot.CHEST); + if (currentElytra.getItem() != Items.ELYTRA) { + warning("Elytra was unequipped during repair!"); + currentState = RepairState.REPAIRING; + return; + } + + if (currentElytra.getDamage() == 0) { + info("Equipped elytra repaired."); + currentState = RepairState.REPAIRING; + return; + } + + if (System.currentTimeMillis() - repairStartTime > repairTimeout.get() * 1000L) { + warning("Repair timeout reached during repair-in-progress."); + currentState = RepairState.RESUMING_FLIGHT; + } + } + + 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 && originalAutoPilotState) { + setModuleSetting(elytraFly, "autoPilot", true); + } + mc.options.forwardKey.setPressed(true); + ElytraController.resume(); + resetRepairState(); + currentState = RepairState.MONITORING; + } + } 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() { + 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")); + } + this.toggle(); + } + + private void initiateRepairSequence() { + wasStashHunterActive = ElytraController.isActive(); + 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) { + error("Initiating emergency disconnect: " + reason); + currentState = RepairState.EMERGENCY_DISCONNECT; + } + + private void resetRepairState() { + repairStartTime = 0; + currentRepairSlot = 0; + elytraSlots.clear(); + 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 void resumeNormalOperation() { + mc.options.sneakKey.setPressed(false); + mc.options.forwardKey.setPressed(false); + + ElytraFly elytraFly = Modules.get().get(ElytraFly.class); + if (elytraFly != null) { + setModuleSetting(elytraFly, "autoPilot", originalAutoPilotState); + setModuleSetting(elytraFly, "autoHover", originalAutoHoverState); + setModuleSetting(elytraFly, "autoTakeOff", originalAutoTakeoffState); + } + + Scaffold scaffold = Modules.get().get(Scaffold.class); + if (scaffold != null && scaffold.isActive()) { + scaffold.toggle(); + setModuleSetting(scaffold, "airPlace", originalScaffoldAirPlace); + setModuleSetting(scaffold, "autoSwitch", originalScaffoldAutoSwitch); + } + + AutoEXP autoExp = Modules.get().get(AutoEXP.class); + if (autoExp != null && autoExp.isActive()) { + autoExp.toggle(); + } + debugLog("AutoElytraRepair cleanup finished."); + } + + // Public Helpers for other modules + public boolean isRepairing() { + return currentState != RepairState.MONITORING && currentState != RepairState.EMERGENCY_DISCONNECT; + } + + public String getCurrentStateName() { + return currentState.name(); + } + + public boolean justFinishedRepair() { + if (justFinishedRepairing) { + justFinishedRepairing = false; + return true; + } + return false; + } + + // Private helpers + private boolean needsRepair(ItemStack elytra) { + if (elytra.getItem() != Items.ELYTRA) return false; + return (elytra.getMaxDamage() - elytra.getDamage()) <= 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 && elytra.getDamage() > 0) return true; + } + return false; + } + + private void selectBestElytraAndEquip() { + // Implementation from user's code + } + + private boolean equipElytraToChestSlot(int slot) { + // Implementation from user's code + return true; + } + + private void debugLog(String message) { + if (debugMode.get()) { + info("[DEBUG] " + message); + } + } + + // 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 null; + } + + @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()); + } + } +} diff --git a/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java b/src/main/java/com/stashhunter/stashhunter/modules/StashHunterModule.java index 5707298..1dab813 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."); @@ -269,47 +272,62 @@ private void onTick(TickEvent.Post event) { tickCounter++; ElytraController.onTick(); + if (autoElytraRepair != null && autoElytraRepair.justFinishedRepair()) { + ElytraController.climbToAltitude(); + } + if (mc.player == null || mc.world == null) { 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..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; @@ -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, @@ -143,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); } } 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