diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0c88085 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,244 @@ +# Contributing to UI-Utils Advanced + +Thank you for your interest in contributing to UI-Utils Advanced! This is a **fork** of the original [UI-Utils](https://github.com/Coderx-Gamer/ui-utils) by Coderx-Gamer, maintained by FrannnDev with additional features and improvements. + +--- + +## About This Fork + +This project is based on the original UI-Utils mod. We maintain compatibility with the original while adding: +- Multi-version support (1.21 - 1.21.11) +- ViaFabricPlus integration +- Additional packet manipulation features +- Improved UI and user experience + +--- + +## Code of Conduct + +By participating in this project, you agree to: + +- Be respectful and inclusive to all contributors +- Provide constructive feedback +- Accept constructive criticism gracefully +- Focus on what is best for the community and project +- Report any unacceptable behavior to the maintainers + +--- + +## How to Contribute + +### Reporting Bugs + +Before submitting a bug report: + +1. Check if the issue already exists in [GitHub Issues](https://github.com/FrannnnDev/ui-utils-advanced/issues) +2. Make sure you're using the latest version +3. Collect relevant information: + - Minecraft version + - Fabric Loader version + - Fabric API version + - Other mods installed (if any) + - Crash log or error message + +When reporting: + +- Use a clear and descriptive title +- Describe the steps to reproduce the issue +- Explain the expected vs actual behavior +- Include screenshots or videos if applicable + +### Suggesting Features + +Feature requests are welcome! Please: + +1. Check if the feature was already requested +2. Provide a clear description of the feature +3. Explain the use case and why it would be useful +4. Consider how it fits with the project's scope + +### Pull Requests + +#### Before Submitting + +1. Fork the repository +2. Create a new branch from `main`: + ```bash + git checkout -b feature/your-feature-name + ``` +3. Make your changes following our coding standards +4. Test your changes on all supported Minecraft versions if possible +5. Commit with clear, descriptive messages + +#### Pull Request Guidelines + +- Keep PRs focused on a single feature or fix +- Update documentation if needed +- Add comments for complex logic +- Follow existing code style and patterns +- Ensure the build passes before submitting + +--- + +## Coding Standards + +### Java Style Guide + +- Use 4 spaces for indentation (no tabs) +- Opening braces on the same line +- Use meaningful variable and method names +- Add Javadoc comments for public methods +- Keep methods short and focused (single responsibility) + +### Example + +```java +/** + * Creates a packet compatible with all Minecraft versions. + * @param onGround Whether the player is on the ground + * @return The constructed packet or null if creation fails + */ +private static PlayerMoveC2SPacket createOnGroundPacket(boolean onGround) { + try { + // Implementation here + } catch (Exception e) { + e.printStackTrace(); + return null; + } +} +``` + +### Mixin Guidelines + +- Use descriptive mixin class names (e.g., `ChatScreenMixin`) +- Document what each mixin modifies +- Use `@Inject` with clear target methods +- Avoid modifying more than necessary +- Test mixins across all supported versions + +--- + +## Multi-Version Compatibility + +UI-Utils supports Minecraft 1.21 through 1.21.11. When contributing: + +1. **Use reflection** for API differences between versions +2. **Test on multiple versions** when possible +3. **Document version-specific behavior** in comments +4. **Never use version-specific code directly** - always use compatibility wrappers + +### Example: Version-Compatible Code + +```java +// BAD - Will fail on older versions +new PlayerMoveC2SPacket.OnGroundOnly(true, false); + +// GOOD - Uses reflection for compatibility +private static PlayerMoveC2SPacket createOnGroundPacket(boolean onGround, boolean collision) { + try { + Constructor constructor = PlayerMoveC2SPacket.OnGroundOnly.class + .getConstructor(boolean.class, boolean.class); + return (PlayerMoveC2SPacket) constructor.newInstance(onGround, collision); + } catch (NoSuchMethodException e) { + // Fallback for older versions + Constructor constructor = PlayerMoveC2SPacket.OnGroundOnly.class + .getConstructor(boolean.class); + return (PlayerMoveC2SPacket) constructor.newInstance(onGround); + } +} +``` + +--- + +## Building the Project + +### Single Version Build + +```bash +./gradlew build +``` + +### Multi-Version Build + +```bash +./build-all.bat +``` + +Output JARs will be in `build/multiversion/` + +--- + +## Commit Message Format + +Use clear, descriptive commit messages: + +``` +: + +[optional body with more details] +``` + +### Types + +| Type | Description | +|------|-------------| +| `feat` | New feature | +| `fix` | Bug fix | +| `docs` | Documentation changes | +| `style` | Code style changes (formatting, etc.) | +| `refactor` | Code refactoring | +| `test` | Adding or updating tests | +| `chore` | Maintenance tasks | + +### Examples + +``` +feat: Add hClip command for horizontal teleportation + +fix: Resolve crash when opening inventory on 1.21.3 + +docs: Update README with new button descriptions + +refactor: Use reflection for multi-version packet compatibility +``` + +--- + +## Review Process + +1. All PRs require at least one maintainer review +2. CI must pass (if configured) +3. Code must follow project standards +4. Changes must be tested on relevant versions +5. Documentation must be updated if needed + +--- + +## License + +By contributing to UI-Utils Advanced, you agree that your contributions will be licensed under the same license as the project (see [LICENSE](LICENSE)). + +**Note:** This fork respects the original license from Coderx-Gamer's UI-Utils. Please ensure your contributions are compatible with both projects. + +--- + +## Credits + +- **Original Author:** [Coderx-Gamer](https://github.com/Coderx-Gamer/ui-utils) +- **Fork Maintainer:** [FrannnDev](https://github.com/FrannnnDev) + +--- + +## Questions? + +If you have questions about contributing: + +1. Check existing issues and discussions +2. Open a new issue with the `question` label +3. Contact maintainers through GitHub + +--- + +Thank you for contributing to UI-Utils Advanced! + + diff --git a/README.md b/README.md index eeaee54..f26b5df 100644 --- a/README.md +++ b/README.md @@ -1,142 +1,272 @@ # UI-Utils -Plugin debugging mod. May be incompatible with mac, needs Fabric API. ---- -# How to use: +Fabric mod for debugging server GUIs and packets. Supports Minecraft 1.21 - 1.21.11. --- -- Open any inventory / container with the mod, and you should see some buttons and a text field. +## Screenshots -![image](images/gui.png) +image +image +image +image -- "Close without packet" closes your current GUI (ScreenHandler) without sending a `CloseHandledScreenC2SPacket` to the server. -- "De-sync" closes your current GUI server-side and keeps it open client-side. +--- -- "Send packets: true/false" tells the client whether it should send any `ClickSlotC2SPacket`(s) and `ButtonClickC2SPacket`(s). +## Credits -- "Delay packets: true/false" when turned on it will store all `ClickSlotC2SPacket`(s) and `ButtonClickC2SPacket`(s) into a list and will not send them immediately until turned off which sends them all at once. +| Role | Author | Link | +|------|--------|------| +| Original Creator | Coderx-Gamer | [GitHub](https://github.com/Coderx-Gamer/ui-utils) | +| Modified by | FrannnDev | [GitHub](https://github.com/FrannnnDev) | -- "Save GUI" saves your current GUI to a variable and can be accessed by pressing a keybinding in the keybinding options (default key is 'V'). +--- -- "Disconnect and send packets" will if "Delay packets" is turned on send the list of stored packets immediately and then disconnect right afterward (can create potential race conditions on non-vanilla servers). +## Downloads -- "Sync Id: ??" is a number used internally to sync various GUI related packets. +| Minecraft | Download | +|-----------|----------| +| 1.21 | [ui_utils-2.4.1-mc1.21.jar](https://github.com/FrannnnDev/ui-utils-advanced/releases/download/v2.4.1/ui_utils-2.4.1-mc1.21.jar) | +| 1.21.1 | [ui_utils-2.4.1-mc1.21.1.jar](https://github.com/FrannnnDev/ui-utils-advanced/releases/download/v2.4.1/ui_utils-2.4.1-mc1.21.1.jar) | +| 1.21.2 | [ui_utils-2.4.1-mc1.21.2.jar](https://github.com/FrannnnDev/ui-utils-advanced/releases/download/v2.4.1/ui_utils-2.4.1-mc1.21.2.jar) | +| 1.21.3 | [ui_utils-2.4.1-mc1.21.3.jar](https://github.com/FrannnnDev/ui-utils-advanced/releases/download/v2.4.1/ui_utils-2.4.1-mc1.21.3.jar) | +| 1.21.4 | [ui_utils-2.4.1-mc1.21.4.jar](https://github.com/FrannnnDev/ui-utils-advanced/releases/download/v2.4.1/ui_utils-2.4.1-mc1.21.4.jar) | +| 1.21.5 | [ui_utils-2.4.1-mc1.21.5.jar](https://github.com/FrannnnDev/ui-utils-advanced/releases/download/v2.4.1/ui_utils-2.4.1-mc1.21.5.jar) | +| 1.21.6 | [ui_utils-2.4.1-mc1.21.6.jar](https://github.com/FrannnnDev/ui-utils-advanced/releases/download/v2.4.1/ui_utils-2.4.1-mc1.21.6.jar) | +| 1.21.7 | [ui_utils-2.4.1-mc1.21.7.jar](https://github.com/FrannnnDev/ui-utils-advanced/releases/download/v2.4.1/ui_utils-2.4.1-mc1.21.7.jar) | +| 1.21.8 | [ui_utils-2.4.1-mc1.21.8.jar](https://github.com/FrannnnDev/ui-utils-advanced/releases/download/v2.4.1/ui_utils-2.4.1-mc1.21.8.jar) | +| 1.21.11 | [ui_utils-2.4.1-mc1.21.11.jar](https://github.com/FrannnnDev/ui-utils-advanced/releases/download/v2.4.1/ui_utils-2.4.1-mc1.21.11.jar) | -- "Revision: ??" is a number used internally to sync various GUI related packets sent from the server to the client. +> **Note:** Versions 1.21.9-1.21.10 are not yet supported due to incomplete Fabric yarn mappings. -- "Fabricate packet" allows you to create a custom `ClickSlotC2SPacket` and `ButtonClickC2SPacket` within a window it creates. +--- -- "Copy GUI Title JSON" copies the name of your current GUI in JSON format. +## Installation -- The text box is a chat field for chatting or running commands while in a GUI. +1. Install Fabric Loader +2. Drop the mod JAR into your `mods` folder (Fabric API is already included) +3. (Optional) Install [ViaFabricPlus](https://modrinth.com/mod/viafabricplus) for multi-version support --- -### Fabricate packet tutorial: +## Inventory Overlay (E Menu) ---- +Open any inventory to see the UI-Utils overlay on the left side. -- `ClickSlotC2SPacket`(s) are what the client sends to the server when clicking any slot in a GUI (e.g. shift clicking an item). +### Info Panel -- When clicking the "Fabricate packet" button you should see this window appear (may not work on OSX): +Located at the top-right of the overlay: -![image](images/packet_option.png) +| Field | Description | +|-------|-------------| +| Sync | Current screen handler sync ID used by packets | +| Rev | Current revision number for packet validation | -- Clicking "Click Slot" will open up this window: +### Button List -![image](images/click_slot_packet.png) +| Button | Description | +|--------|-------------| +| Close without packet | Closes GUI client-side only. Server thinks GUI is still open | +| Desync | Sends close packet to server but keeps GUI open client-side | +| Send packets: true/false | Toggle whether click packets are sent to server | +| Delay packets: true/false | When ON, stores all click packets in queue instead of sending | +| Leave & Send Packets | Sends all queued packets at once, then closes GUI | +| Fabricate packet | Opens external window to create custom packets (not available on Mac) | +| Copy GUI Title JSON | Copies current GUI title as JSON to clipboard | +| Save GUI / Load GUI | Save current GUI state to memory or restore it | +| Clear Queue / Queue: N | Clear all queued packets or show current queue count | +| Resync Inv / Disconnect | Force inventory resync or disconnect from server | +| [-] Spam (xN) [+] | Send all queued packets N times. Use - and + to adjust multiplier | +| Send One / Pop Last | Send first packet from queue or remove last packet without sending | -- Enter the "Sync Id" and "Revision" value you see in the in-game GUI to the "Click Slot Packet" GUI. +### Chat Input -- The "Slot" value should be set to what slot you would like to click (starting from 0) you can generally find the location of GUI slots on Google for generic GUI(s), e.g. double chest: +At the bottom-left of any GUI there is a chat input field: - ![image](images/slots.png) +- Type messages --> sends to server chat +- Start with `/` --> sends as server command +- Start with `,` --> executes UI-Utils command -- The "Button" field should be set to either: (0 is a left-click, 1 is a right-click, 0-8 and 40 will be explained below). +--- -- The "Action" field should be set to one of these options, +## Multiplayer Screen -![image](images/click_slot_actions.png) +Custom buttons appear at the top of the multiplayer server list. -- "PICKUP" puts the item on the slot field on your cursor or vice versa, "QUICK_MOVE" is a shift click, "SWAP" acts as a hotbar or offhand swap (e.g. if your "Action" is set to "SWAP" and the "Button" set to 0-8, it will swap the item in the "Slot" field to one of those hotbar slots (starting from 0) or vice versa, "Button" being set to 40 will swap the item in the "Slot" field to your offhand or vice versa), "CLONE" acts as a middle click to clone items (only works in creative mode), "THROW" drops the item in the "Slot" field, "QUICK_CRAFT" is a bit complicated, so you will have to experiment yourself or look into some code for it, "PICKUP_ALL" will pick up all the items matching to the item on your cursor, as long as "Slot" is within bounds of that GUI. +### Left Side - Resource Pack -- The "Send" button will send the packet for with all the info you inputted and will give a response if it was successful or failed to send the packet you provided. +| Button | Description | +|--------|-------------| +| Bypass | Bypass required resource packs. Tells server pack was loaded without downloading. Green when active | +| Deny | Force deny all resource packs. Use if Bypass alone still kicks you. Green when active | -- The "Times to send" field tells the client how many times to send that packet when pressing "Send". +### Right Side - Utilities -- The delay checkbox tells the client if the packet should wait while "Delay Packets" is on. +| Button | Description | +|--------|-------------| +| User | Opens popup to change session username. Temporary until game restart | +| Via+ | Opens ViaFabricPlus version selector. Requires ViaFabricPlus mod installed | --- -- Example of this feature: +## Keybinds + +| Key | Action | +|-----|--------| +| V | Load saved GUI (configurable in controls menu) | -![image](images/click_slot_example.png) +--- -- When clicking "Send" in the above image, it should drop the bedrock item on the ground. +## Commands -![image](images/click_slot_example_2.png) +All commands use `,` prefix by default. Change with `,prefix `. +### General -- Fabricate packet tutorial (Button Click): ---- +| Command | Usage | Description | +|---------|-------|-------------| +| help | `,help [command]` | List all commands or show help for specific command | +| man | `,man ` | Show detailed manual for a command | +| toggleuiutils | `,toggleuiutils` | Toggle UI-Utils overlay on/off | +| prefix | `,prefix [new]` | View or change command prefix | +| echo | `,echo ` | Print text to chat (client-side only) | -- `ButtonClickC2SPacket`(s) are what the client sends to the server when clicking a button in a server-side GUI (e.g. clicking an enchantment in an enchantment table). +### Math -- When clicking the "Fabricate packet" button you should see this window appear: +| Command | Usage | Description | +|---------|-------|-------------| +| math | `,math ` | Calculate math expression. Supports + - * / ^ () | -![image](images/packet_option.png) +Example: +``` +,math 2+2*3 +--> Result: 8.0 +``` -- Clicking "Button Click" will open up this window: +### Screen Control -![image](images/button_click_packet.png) +| Command | Usage | Description | +|---------|-------|-------------| +| close | `,close` | Close current screen without sending packet | +| desync | `,desync` | Send close packet but keep GUI open client-side | -- Enter the "Sync Id" field in the "Button Click Packet" GUI as the "Sync Id" value you will see in the in-game GUI. +### Chat -- Enter the "Button Id" field as what button you would like to click in a GUI (starting from 0.) +| Command | Usage | Description | +|---------|-------|-------------| +| chat | `,chat ` | Send message or command to server from anywhere | -- Enter into the field "Times to send" (by default it's one) the amount of packets you would like to send. +Example: +``` +,chat /spawn +--> Sends /spawn command to server +``` -- Check the "Delay" checkbox if you would like to delay your packet while "Delay Packets" is on. +### Server -- Example of this feature: ---- +| Command | Usage | Description | +|---------|-------|-------------| +| joinserver | `,joinserver ` | Connect to another server | +| plugins | `,plugins` | Scan server plugins via tab-complete exploit | -![image](images/enchant.png) +Plugins output: +- Normal plugins --> red text +- Anticheats/exploit-related --> dark red text -![image](images/enchant_2.png) +### Screen Management -![image](images/enchant_3.png) +| Command | Usage | Description | +|---------|-------|-------------| +| screen save | `,screen save ` | Save current screen to named slot | +| screen load | `,screen load ` | Load saved screen from slot | +| screen list | `,screen list` | List all saved screen slots | +| screen info | `,screen info ` | Show info about saved screen | ---- +### Account -## "^toggleuiutils" command: +| Command | Usage | Description | +|---------|-------|-------------| +| account dump | `,account dump` | Show current username and UUID | +| account set username | `,account set username ` | Change session username | +| account set uuid | `,account set uuid ` | Change session UUID | ---- -- Enables/Disables UI-Utils in-game GUI rendering. ---- +Note: Account changes are temporary and reset on game restart. -## Resource Pack Bypassing +### Movement ---- +| Command | Usage | Description | +|---------|-------|-------------| +| vclip | `,vclip ` | Teleport vertically. Positive = up, negative = down | +| hclip | `,hclip ` | Teleport horizontally in look direction | -You can bypass a required resource pack on a server by turning on "Bypass Resource Pack" in the multiplayer screen, if you still get kicked, enable "Force Deny", which will block all resource packs. +Examples: +``` +,vclip 5 +--> Teleport 5 blocks up + +,vclip -3 +--> Teleport 3 blocks down + +,hclip 10 +--> Teleport 10 blocks forward +``` --- -## Building: +## Fabricate Packet + +External window for creating custom packets. + +### ClickSlotC2SPacket + +Used for clicking slots in GUIs. + +| Field | Description | +|-------|-------------| +| Sync Id | From overlay info panel | +| Revision | From overlay info panel | +| Slot | Slot number to click (starts at 0) | +| Button | 0 = left click, 1 = right click, 0-8 = hotbar slot, 40 = offhand | +| Action | PICKUP, QUICK_MOVE, SWAP, CLONE, THROW, QUICK_CRAFT, PICKUP_ALL | +| Times to send | Number of packets to send | +| Delay | Check to add packet to delay queue instead of sending | + +### ButtonClickC2SPacket + +Used for clicking buttons in server GUIs (enchantment tables, stonecutters, etc). + +| Field | Description | +|-------|-------------| +| Sync Id | From overlay info panel | +| Button Id | Button number to click (starts at 0) | +| Times to send | Number of packets to send | +| Delay | Check to add packet to delay queue instead of sending | --- -You can find building instructions on the UI-Utils website (https://ui-utils.com). +## Toggle + +Type `,toggleuiutils` in chat or any GUI chat input to enable or disable the mod overlay. --- -This mod concept is not an original idea, there are similar mods out there that have similar features, the goal of this mod is to be on the latest version and have some additional features. +## Building + +### Single Version +``` +.\gradlew build +``` -Some images here may be outdated. +### All Versions +``` +.\build-all.bat +``` + +Output JARs will be in `build/libs/` (single) or `releases` (all versions). + +--- -Note that mac may work with most of the mod but not the fabricate packet feature potentially. +## License -That's all the features for this mod currently, there may be more updates in the future but I cannot guarantee. +This mod is licensed under CC BY-NC-SA 4.0. See [LICENSE](LICENSE) file. diff --git a/build-all.bat b/build-all.bat new file mode 100644 index 0000000..4ab8986 --- /dev/null +++ b/build-all.bat @@ -0,0 +1,86 @@ +@echo off +setlocal enabledelayedexpansion + +echo UI-Utils Multi-Version Build Script +echo. + +call gradlew.bat --stop 2>nul + +md "releases" 2>nul + +set "versions[0]=1.21,1.21+build.9,0.102.0+1.21" +set "versions[1]=1.21.1,1.21.1+build.3,0.105.0+1.21.1" +set "versions[2]=1.21.2,1.21.2+build.1,0.106.0+1.21.2" +set "versions[3]=1.21.3,1.21.3+build.2,0.114.0+1.21.3" +set "versions[4]=1.21.4,1.21.4+build.8,0.114.0+1.21.4" +set "versions[5]=1.21.5,1.21.5+build.1,0.127.1+1.21.5" +set "versions[6]=1.21.6,1.21.6+build.1,0.128.1+1.21.6" +set "versions[7]=1.21.7,1.21.7+build.8,0.128.2+1.21.7" +REM Versions 1.21.8-1.21.11 - yarn mappings incomplete + +set "total=8" +set "success=0" +set "failed=0" + +copy "gradle.properties" "gradle.properties.backup" >nul + +for /L %%i in (0,1,7) do ( + for /f "tokens=1,2,3 delims=," %%a in ("!versions[%%i]!") do ( + set "mc_ver=%%a" + set "yarn=%%b" + set "fabric=%%c" + + echo. + echo Building for Minecraft !mc_ver! + + ( + echo # Done to increase the memory available to gradle. + echo org.gradle.jvmargs=-Xmx4G + echo org.gradle.parallel=true + echo. + echo # Fabric Properties + echo minecraft_version=!mc_ver! + echo yarn_mappings=!yarn! + echo loader_version=0.18.4 + echo. + echo # Fabric API + echo fabric_version=!fabric! + echo # Mod Properties + echo mod_version = 2.4.1 + echo. + echo maven_group = com.ui_utils + echo archives_base_name = ui_utils + ) > gradle.properties + + call gradlew.bat --stop 2>nul + timeout /t 2 /nobreak >nul + + call gradlew.bat build --quiet --no-daemon + + if !errorlevel! equ 0 ( + if exist "build\libs\ui_utils-2.4.1.jar" ( + copy "build\libs\ui_utils-2.4.1.jar" "releases\ui_utils-2.4.1-mc!mc_ver!.jar" >nul + echo [OK] Minecraft !mc_ver! - ui_utils-2.4.1-mc!mc_ver!.jar + ) else ( + echo [OK] Minecraft !mc_ver! - JAR built but copy failed + ) + set /a success+=1 + ) else ( + echo [FAIL] Minecraft !mc_ver! build failed + set /a failed+=1 + ) + ) +) + +move /y "gradle.properties.backup" "gradle.properties" >nul + +echo. +echo Success: !success! +echo Failed: !failed! +echo. +echo JARs are in: releases\ +echo. + +dir /b "releases\*.jar" 2>nul + +pause diff --git a/build.gradle b/build.gradle index 295dded..8562474 100644 --- a/build.gradle +++ b/build.gradle @@ -19,18 +19,10 @@ repositories { } dependencies { - // To change the versions see the gradle.properties file minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - - // Uncomment the following line to enable the deprecated Fabric API modules. - // These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time. - - // modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}" + modImplementation(include("net.fabricmc.fabric-api:fabric-api:${project.fabric_version}")) } processResources { @@ -62,7 +54,6 @@ loom { accessWidenerPath = file("src/main/resources/ui_utils.accesswidener") } -// configure the maven publication publishing { publications { mavenJava(MavenPublication) { diff --git a/gradle.properties b/gradle.properties index f0c9177..5781536 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,21 +3,14 @@ org.gradle.jvmargs=-Xmx4G org.gradle.parallel=true # Fabric Properties -# check these on https://fabricmc.net/develop minecraft_version=1.21.7 -yarn_mappings=1.21.7+build.6 -loader_version=0.16.14 +yarn_mappings=1.21.7+build.8 +loader_version=0.18.4 # Fabric API fabric_version=0.128.2+1.21.7 # Mod Properties -mod_version = 2.4.0 - -#x.y.z - -#x -- major mc release -#y -- minor mc release -#z -- ui utils release +mod_version = 2.4.1 maven_group = com.ui_utils archives_base_name = ui_utils diff --git a/images/button_click_packet.png b/images/button_click_packet.png deleted file mode 100644 index 7727151..0000000 Binary files a/images/button_click_packet.png and /dev/null differ diff --git a/images/click_slot_actions.png b/images/click_slot_actions.png deleted file mode 100644 index f29e601..0000000 Binary files a/images/click_slot_actions.png and /dev/null differ diff --git a/images/click_slot_example.png b/images/click_slot_example.png deleted file mode 100644 index 5fe14a5..0000000 Binary files a/images/click_slot_example.png and /dev/null differ diff --git a/images/click_slot_example_2.png b/images/click_slot_example_2.png deleted file mode 100644 index 53ef722..0000000 Binary files a/images/click_slot_example_2.png and /dev/null differ diff --git a/images/click_slot_packet.png b/images/click_slot_packet.png deleted file mode 100644 index d43904c..0000000 Binary files a/images/click_slot_packet.png and /dev/null differ diff --git a/images/enchant.png b/images/enchant.png deleted file mode 100644 index d0bd302..0000000 Binary files a/images/enchant.png and /dev/null differ diff --git a/images/enchant_2.png b/images/enchant_2.png deleted file mode 100644 index fecb879..0000000 Binary files a/images/enchant_2.png and /dev/null differ diff --git a/images/enchant_3.png b/images/enchant_3.png deleted file mode 100644 index 1b16b1d..0000000 Binary files a/images/enchant_3.png and /dev/null differ diff --git a/images/gui.png b/images/gui.png deleted file mode 100644 index 6508213..0000000 Binary files a/images/gui.png and /dev/null differ diff --git a/images/packet_option.png b/images/packet_option.png deleted file mode 100644 index 05e2211..0000000 Binary files a/images/packet_option.png and /dev/null differ diff --git a/images/slots.png b/images/slots.png deleted file mode 100644 index 9e4dd83..0000000 Binary files a/images/slots.png and /dev/null differ diff --git a/src/main/java/com/ui_utils/GithubRelease.java b/src/main/java/com/ui_utils/GithubRelease.java index b89ce5d..6e56dbe 100644 --- a/src/main/java/com/ui_utils/GithubRelease.java +++ b/src/main/java/com/ui_utils/GithubRelease.java @@ -14,7 +14,6 @@ public String getTagName() { private String name; public String getMcVersion() { - // in name, return the text within the () return name.substring(name.indexOf("(") + 1, name.indexOf(")")); } } diff --git a/src/main/java/com/ui_utils/MainClient.java b/src/main/java/com/ui_utils/MainClient.java index 6e50a03..ab096a7 100644 --- a/src/main/java/com/ui_utils/MainClient.java +++ b/src/main/java/com/ui_utils/MainClient.java @@ -1,535 +1,611 @@ -package com.ui_utils; - -import com.google.common.collect.ImmutableList; -import com.google.gson.Gson; -import com.mojang.serialization.JsonOps; -import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.metadata.ModMetadata; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.option.KeyBinding; -import net.minecraft.client.util.InputUtil; -import net.minecraft.network.packet.Packet; -import net.minecraft.network.packet.c2s.play.ButtonClickC2SPacket; -import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket; -import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket; -import net.minecraft.screen.slot.SlotActionType; -import net.minecraft.screen.sync.ItemStackHash; -import net.minecraft.text.Text; -import net.minecraft.text.TextCodecs; -import org.jetbrains.annotations.NotNull; -import org.lwjgl.glfw.GLFW; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.ui_utils.mixin.accessor.ClientConnectionAccessor; - -import javax.swing.*; -import java.awt.*; -import java.util.Timer; -import java.util.TimerTask; -import java.util.Vector; - -public class MainClient implements ClientModInitializer { - public static Font monospace; - public static Color darkWhite; - - public static KeyBinding restoreScreenKey; - - public static Logger LOGGER = LoggerFactory.getLogger("ui-utils"); - public static MinecraftClient mc = MinecraftClient.getInstance(); - @Override - public void onInitializeClient() { - UpdateUtils.checkForUpdates(); - - // register "restore screen" key - restoreScreenKey = KeyBindingHelper.registerKeyBinding(new KeyBinding("Restore Screen", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_V, "UI Utils")); - - // register event for END_CLIENT_TICK - ClientTickEvents.END_CLIENT_TICK.register((client) -> { - // detect if the "restore screen" keybinding is pressed - while (restoreScreenKey.wasPressed()) { - if (SharedVariables.storedScreen != null && SharedVariables.storedScreenHandler != null && client.player != null) { - client.setScreen(SharedVariables.storedScreen); - client.player.currentScreenHandler = SharedVariables.storedScreenHandler; - } - } - }); - - // set java.awt.headless to false if os is not mac (allows for JFrame guis to be used) - if (!MinecraftClient.IS_SYSTEM_MAC) { - System.setProperty("java.awt.headless", "false"); - monospace = new Font(Font.MONOSPACED, Font.PLAIN, 10); - darkWhite = new Color(220, 220, 220); - } - } - - @SuppressWarnings("all") - public static void createText(MinecraftClient mc, DrawContext context, TextRenderer textRenderer) { - // display the current gui's sync id, revision - context.drawText(textRenderer, "Sync Id: " + mc.player.currentScreenHandler.syncId, 200, 5, Color.WHITE.getRGB(), false); - context.drawText(textRenderer, "Revision: " + mc.player.currentScreenHandler.getRevision(), 200, 35, Color.WHITE.getRGB(), false); - } - - // bro are you ever going to clean this up? - // this code is very messy, ill clean it up if you dont - // -- MrBreakNFix - public static void createWidgets(MinecraftClient mc, Screen screen) { - // register "close without packet" button in all HandledScreens - screen.addDrawableChild(ButtonWidget.builder(Text.of("Close without packet"), (button) -> { - // closes the current gui without sending a packet to the current server - mc.setScreen(null); - }).width(115).position(5, 5).build()); - - // register "de-sync" button in all HandledScreens - screen.addDrawableChild(ButtonWidget.builder(Text.of("De-sync"), (button) -> { - // keeps the current gui open client-side and closed server-side - if (mc.getNetworkHandler() != null && mc.player != null) { - mc.getNetworkHandler().sendPacket(new CloseHandledScreenC2SPacket(mc.player.currentScreenHandler.syncId)); - } else { - LOGGER.warn("Minecraft network handler or player was null while using 'De-sync' in UI Utils."); - } - }).width(115).position(5, 35).build()); - - // register "send packets" button in all HandledScreens - screen.addDrawableChild(ButtonWidget.builder(Text.of("Send packets: " + SharedVariables.sendUIPackets), (button) -> { - // tells the client if it should send any gui related packets - SharedVariables.sendUIPackets = !SharedVariables.sendUIPackets; - button.setMessage(Text.of("Send packets: " + SharedVariables.sendUIPackets)); - }).width(115).position(5, 65).build()); - - // register "delay packets" button in all HandledScreens - screen.addDrawableChild(ButtonWidget.builder(Text.of("Delay packets: " + SharedVariables.delayUIPackets), (button) -> { - // toggles a setting to delay all gui related packets to be used later when turning this setting off - SharedVariables.delayUIPackets = !SharedVariables.delayUIPackets; - button.setMessage(Text.of("Delay packets: " + SharedVariables.delayUIPackets)); - if (!SharedVariables.delayUIPackets && !SharedVariables.delayedUIPackets.isEmpty() && mc.getNetworkHandler() != null) { - for (Packet packet : SharedVariables.delayedUIPackets) { - mc.getNetworkHandler().sendPacket(packet); - } - if (mc.player != null) { - mc.player.sendMessage(Text.of("Sent " + SharedVariables.delayedUIPackets.size() + " packets."), false); - } - SharedVariables.delayedUIPackets.clear(); - } - }).width(115).position(5, 95).build()); - - // register "save gui" button in all HandledScreens - screen.addDrawableChild(ButtonWidget.builder(Text.of("Save GUI"), (button) -> { - // saves the current gui to a variable to be accessed later - if (mc.player != null) { - SharedVariables.storedScreen = mc.currentScreen; - SharedVariables.storedScreenHandler = mc.player.currentScreenHandler; - } - }).width(115).position(5, 125).build()); - - // register "disconnect and send packets" button in all HandledScreens - screen.addDrawableChild(ButtonWidget.builder(Text.of("Disconnect and send packets"), (button) -> { - // sends all "delayed" gui related packets before disconnecting, use: potential race conditions on non-vanilla servers - SharedVariables.delayUIPackets = false; - if (mc.getNetworkHandler() != null) { - for (Packet packet : SharedVariables.delayedUIPackets) { - mc.getNetworkHandler().sendPacket(packet); - } - mc.getNetworkHandler().getConnection().disconnect(Text.of("Disconnecting (UI-UTILS)")); - } else { - LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) is null while client is disconnecting."); - } - SharedVariables.delayedUIPackets.clear(); - }).width(160).position(5, 155).build()); - - // register "fabricate packet" button in all HandledScreens - ButtonWidget fabricatePacketButton = ButtonWidget.builder(Text.of("Fabricate packet"), (button) -> { - // creates a gui allowing you to fabricate packets - - JFrame frame = new JFrame("Choose Packet"); - frame.setBounds(0, 0, 450, 100); - frame.setResizable(false); - frame.setLocationRelativeTo(null); - frame.setLayout(null); - - JButton clickSlotButton = getPacketOptionButton("Click Slot"); - clickSlotButton.setBounds(100, 25, 110, 20); - clickSlotButton.addActionListener((event) -> { - // im too lazy to comment everything here just read the code yourself - frame.setVisible(false); - - JFrame clickSlotFrame = new JFrame("Click Slot Packet"); - clickSlotFrame.setBounds(0, 0, 450, 300); - clickSlotFrame.setResizable(false); - clickSlotFrame.setLocationRelativeTo(null); - clickSlotFrame.setLayout(null); - - JLabel syncIdLabel = new JLabel("Sync Id:"); - syncIdLabel.setFocusable(false); - syncIdLabel.setFont(monospace); - syncIdLabel.setBounds(25, 25, 100, 20); - - JLabel revisionLabel = new JLabel("Revision:"); - revisionLabel.setFocusable(false); - revisionLabel.setFont(monospace); - revisionLabel.setBounds(25, 50, 100, 20); - - JLabel slotLabel = new JLabel("Slot:"); - slotLabel.setFocusable(false); - slotLabel.setFont(monospace); - slotLabel.setBounds(25, 75, 100, 20); - - JLabel buttonLabel = new JLabel("Button:"); - buttonLabel.setFocusable(false); - buttonLabel.setFont(monospace); - buttonLabel.setBounds(25, 100, 100, 20); - - JLabel actionLabel = new JLabel("Action:"); - actionLabel.setFocusable(false); - actionLabel.setFont(monospace); - actionLabel.setBounds(25, 125, 100, 20); - - JLabel timesToSendLabel = new JLabel("Times to send:"); - timesToSendLabel.setFocusable(false); - timesToSendLabel.setFont(monospace); - timesToSendLabel.setBounds(25, 190, 100, 20); - - JTextField syncIdField = new JTextField(1); - syncIdField.setFont(monospace); - syncIdField.setBounds(125, 25, 100, 20); - - JTextField revisionField = new JTextField(1); - revisionField.setFont(monospace); - revisionField.setBounds(125, 50, 100, 20); - - JTextField slotField = new JTextField(1); - slotField.setFont(monospace); - slotField.setBounds(125, 75, 100, 20); - - JTextField buttonField = new JTextField(1); - buttonField.setFont(monospace); - buttonField.setBounds(125, 100, 100, 20); - - JComboBox actionField = new JComboBox<>(new Vector<>(ImmutableList.of( - "PICKUP", - "QUICK_MOVE", - "SWAP", - "CLONE", - "THROW", - "QUICK_CRAFT", - "PICKUP_ALL" - ))); - actionField.setFocusable(false); - actionField.setEditable(false); - actionField.setBorder(BorderFactory.createEmptyBorder()); - actionField.setBackground(darkWhite); - actionField.setFont(monospace); - actionField.setBounds(125, 125, 100, 20); - - JLabel statusLabel = new JLabel(); - statusLabel.setVisible(false); - statusLabel.setFocusable(false); - statusLabel.setFont(monospace); - statusLabel.setBounds(210, 150, 190, 20); - - JCheckBox delayBox = new JCheckBox("Delay"); - delayBox.setBounds(115, 150, 85, 20); - delayBox.setSelected(false); - delayBox.setFont(monospace); - delayBox.setFocusable(false); - - JTextField timesToSendField = new JTextField("1"); - timesToSendField.setFont(monospace); - timesToSendField.setBounds(125, 190, 100, 20); - - JButton sendButton = new JButton("Send"); - sendButton.setFocusable(false); - sendButton.setBounds(25, 150, 75, 20); - sendButton.setBorder(BorderFactory.createEtchedBorder()); - sendButton.setBackground(darkWhite); - sendButton.setFont(monospace); - sendButton.addActionListener((event0) -> { - if ( - MainClient.isInteger(syncIdField.getText()) && - MainClient.isInteger(revisionField.getText()) && - MainClient.isInteger(slotField.getText()) && - MainClient.isInteger(buttonField.getText()) && - MainClient.isInteger(timesToSendField.getText()) && - actionField.getSelectedItem() != null) { - int syncId = Integer.parseInt(syncIdField.getText()); - int revision = Integer.parseInt(revisionField.getText()); - short slot = Short.parseShort(slotField.getText()); - byte button0 = Byte.parseByte(buttonField.getText()); - SlotActionType action = MainClient.stringToSlotActionType(actionField.getSelectedItem().toString()); - int timesToSend = Integer.parseInt(timesToSendField.getText()); - - if (action != null) { - ClickSlotC2SPacket packet = new ClickSlotC2SPacket(syncId, revision, slot, button0, action, new Int2ObjectArrayMap<>(), ItemStackHash.EMPTY); - try { - Runnable toRun = getFabricatePacketRunnable(mc, delayBox.isSelected(), packet); - for (int i = 0; i < timesToSend; i++) { - toRun.run(); - } - } catch (Exception e) { - statusLabel.setForeground(Color.RED.darker()); - statusLabel.setText("You must be connected to a server!"); - MainClient.queueTask(() -> { - statusLabel.setVisible(false); - statusLabel.setText(""); - }, 1500L); - return; - } - statusLabel.setVisible(true); - statusLabel.setForeground(Color.GREEN.darker()); - statusLabel.setText("Sent successfully!"); - MainClient.queueTask(() -> { - statusLabel.setVisible(false); - statusLabel.setText(""); - }, 1500L); - } else { - statusLabel.setVisible(true); - statusLabel.setForeground(Color.RED.darker()); - statusLabel.setText("Invalid arguments!"); - MainClient.queueTask(() -> { - statusLabel.setVisible(false); - statusLabel.setText(""); - }, 1500L); - } - } else { - statusLabel.setVisible(true); - statusLabel.setForeground(Color.RED.darker()); - statusLabel.setText("Invalid arguments!"); - MainClient.queueTask(() -> { - statusLabel.setVisible(false); - statusLabel.setText(""); - }, 1500L); - } - }); - - clickSlotFrame.add(syncIdLabel); - clickSlotFrame.add(revisionLabel); - clickSlotFrame.add(slotLabel); - clickSlotFrame.add(buttonLabel); - clickSlotFrame.add(actionLabel); - clickSlotFrame.add(timesToSendLabel); - clickSlotFrame.add(syncIdField); - clickSlotFrame.add(revisionField); - clickSlotFrame.add(slotField); - clickSlotFrame.add(buttonField); - clickSlotFrame.add(actionField); - clickSlotFrame.add(sendButton); - clickSlotFrame.add(statusLabel); - clickSlotFrame.add(delayBox); - clickSlotFrame.add(timesToSendField); - clickSlotFrame.setVisible(true); - }); - - JButton buttonClickButton = getPacketOptionButton("Button Click"); - buttonClickButton.setBounds(250, 25, 110, 20); - buttonClickButton.addActionListener((event) -> { - frame.setVisible(false); - - JFrame buttonClickFrame = new JFrame("Button Click Packet"); - buttonClickFrame.setBounds(0, 0, 450, 250); - buttonClickFrame.setResizable(false); - buttonClickFrame.setLocationRelativeTo(null); - buttonClickFrame.setLayout(null); - - JLabel syncIdLabel = new JLabel("Sync Id:"); - syncIdLabel.setFocusable(false); - syncIdLabel.setFont(monospace); - syncIdLabel.setBounds(25, 25, 100, 20); - - JLabel buttonIdLabel = new JLabel("Button Id:"); - buttonIdLabel.setFocusable(false); - buttonIdLabel.setFont(monospace); - buttonIdLabel.setBounds(25, 50, 100, 20); - - JTextField syncIdField = new JTextField(1); - syncIdField.setFont(monospace); - syncIdField.setBounds(125, 25, 100, 20); - - JTextField buttonIdField = new JTextField(1); - buttonIdField.setFont(monospace); - buttonIdField.setBounds(125, 50, 100, 20); - - JLabel statusLabel = new JLabel(); - statusLabel.setVisible(false); - statusLabel.setFocusable(false); - statusLabel.setFont(monospace); - statusLabel.setBounds(210, 95, 190, 20); - - JCheckBox delayBox = new JCheckBox("Delay"); - delayBox.setBounds(115, 95, 85, 20); - delayBox.setSelected(false); - delayBox.setFont(monospace); - delayBox.setFocusable(false); - - JLabel timesToSendLabel = new JLabel("Times to send:"); - timesToSendLabel.setFocusable(false); - timesToSendLabel.setFont(monospace); - timesToSendLabel.setBounds(25, 130, 100, 20); - - JTextField timesToSendField = new JTextField("1"); - timesToSendField.setFont(monospace); - timesToSendField.setBounds(125, 130, 100, 20); - - JButton sendButton = new JButton("Send"); - sendButton.setFocusable(false); - sendButton.setBounds(25, 95, 75, 20); - sendButton.setBorder(BorderFactory.createEtchedBorder()); - sendButton.setBackground(darkWhite); - sendButton.setFont(monospace); - sendButton.addActionListener((event0) -> { - if ( - MainClient.isInteger(syncIdField.getText()) && - MainClient.isInteger(buttonIdField.getText()) && - MainClient.isInteger(timesToSendField.getText())) { - int syncId = Integer.parseInt(syncIdField.getText()); - int buttonId = Integer.parseInt(buttonIdField.getText()); - int timesToSend = Integer.parseInt(timesToSendField.getText()); - - ButtonClickC2SPacket packet = new ButtonClickC2SPacket(syncId, buttonId); - try { - Runnable toRun = getFabricatePacketRunnable(mc, delayBox.isSelected(), packet); - for (int i = 0; i < timesToSend; i++) { - toRun.run(); - } - } catch (Exception e) { - statusLabel.setVisible(true); - statusLabel.setForeground(Color.RED.darker()); - statusLabel.setText("You must be connected to a server!"); - MainClient.queueTask(() -> { - statusLabel.setVisible(false); - statusLabel.setText(""); - }, 1500L); - return; - } - statusLabel.setVisible(true); - statusLabel.setForeground(Color.GREEN.darker()); - statusLabel.setText("Sent successfully!"); - MainClient.queueTask(() -> { - statusLabel.setVisible(false); - statusLabel.setText(""); - }, 1500L); - } else { - statusLabel.setVisible(true); - statusLabel.setForeground(Color.RED.darker()); - statusLabel.setText("Invalid arguments!"); - MainClient.queueTask(() -> { - statusLabel.setVisible(false); - statusLabel.setText(""); - }, 1500L); - } - }); - - buttonClickFrame.add(syncIdLabel); - buttonClickFrame.add(buttonIdLabel); - buttonClickFrame.add(syncIdField); - buttonClickFrame.add(timesToSendLabel); - buttonClickFrame.add(buttonIdField); - buttonClickFrame.add(sendButton); - buttonClickFrame.add(statusLabel); - buttonClickFrame.add(delayBox); - buttonClickFrame.add(timesToSendField); - buttonClickFrame.setVisible(true); - }); - - frame.add(clickSlotButton); - frame.add(buttonClickButton); - frame.setVisible(true); - }).width(115).position(5, 185).build(); - fabricatePacketButton.active = !MinecraftClient.IS_SYSTEM_MAC; - screen.addDrawableChild(fabricatePacketButton); - - screen.addDrawableChild(ButtonWidget.builder(Text.of("Copy GUI Title JSON"), (button) -> { - try { - if (mc.currentScreen == null) { - throw new IllegalStateException("The current minecraft screen (mc.currentScreen) is null"); - } - // fixes #137 - // From fabric wiki https://docs.fabricmc.net/develop/text-and-translations#serializing-text - mc.keyboard.setClipboard(new Gson().toJson(TextCodecs.CODEC.encodeStart(JsonOps.INSTANCE, mc.currentScreen.getTitle()).getOrThrow())); - } catch (IllegalStateException e) { - LOGGER.error("Error while copying title JSON to clipboard", e); - } - }).width(115).position(5, 215).build()); - } - - @NotNull - private static JButton getPacketOptionButton(String label) { - JButton button = new JButton(label); - button.setFocusable(false); - button.setBorder(BorderFactory.createEtchedBorder()); - button.setBackground(darkWhite); - button.setFont(monospace); - return button; - } - - @NotNull - private static Runnable getFabricatePacketRunnable(MinecraftClient mc, boolean delay, Packet packet) { - Runnable toRun; - if (delay) { - toRun = () -> { - if (mc.getNetworkHandler() != null) { - mc.getNetworkHandler().sendPacket(packet); - } else { - LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) is null while sending fabricated packets."); - } - }; - } else { - toRun = () -> { - if (mc.getNetworkHandler() != null) { - mc.getNetworkHandler().sendPacket(packet); - } else { - LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) is null while sending fabricated packets."); - } - ((ClientConnectionAccessor) mc.getNetworkHandler().getConnection()).getChannel().writeAndFlush(packet); - }; - } - return toRun; - } - - public static boolean isInteger(String string) { - try { - Integer.parseInt(string); - return true; - } catch (Exception e) { - return false; - } - } - - public static SlotActionType stringToSlotActionType(String string) { - // converts a string to SlotActionType - return switch (string) { - case "PICKUP" -> SlotActionType.PICKUP; - case "QUICK_MOVE" -> SlotActionType.QUICK_MOVE; - case "SWAP" -> SlotActionType.SWAP; - case "CLONE" -> SlotActionType.CLONE; - case "THROW" -> SlotActionType.THROW; - case "QUICK_CRAFT" -> SlotActionType.QUICK_CRAFT; - case "PICKUP_ALL" -> SlotActionType.PICKUP_ALL; - default -> null; - }; - } - - public static void queueTask(Runnable runnable, long delayMs) { - // queues a task for minecraft to run - Timer timer = new Timer(); - TimerTask task = new TimerTask() { - @Override - public void run() { - MinecraftClient.getInstance().send(runnable); - } - }; - timer.schedule(task, delayMs); - } - - public static String getModVersion(String modId) { - ModMetadata modMetadata = FabricLoader.getInstance().getModContainer(modId).isPresent() ? FabricLoader.getInstance().getModContainer(modId).get().getMetadata() : null; - - return modMetadata != null ? modMetadata.getVersion().getFriendlyString() : "null"; - } -} +package com.ui_utils; + +import com.google.common.collect.ImmutableList; +import com.ui_utils.gui.CustomButtonWidget; +import com.ui_utils.gui.UITheme; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.metadata.ModMetadata; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.c2s.play.ButtonClickC2SPacket; +import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket; +import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.text.Text; +import org.jetbrains.annotations.NotNull; +import org.lwjgl.glfw.GLFW; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.ui_utils.mixin.accessor.ClientConnectionAccessor; + +import javax.swing.*; +import java.awt.*; +import java.lang.reflect.Constructor; +import java.util.Timer; +import java.util.TimerTask; +import java.util.Vector; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; + +public class MainClient implements ClientModInitializer { + public static Font monospace; + public static Color darkWhite; + + public static KeyBinding restoreScreenKey; + + public static Logger LOGGER = LoggerFactory.getLogger("ui-utils"); + public static MinecraftClient mc = MinecraftClient.getInstance(); + @Override + public void onInitializeClient() { + UpdateUtils.checkForUpdates(); + + restoreScreenKey = KeyBindingHelper.registerKeyBinding(new KeyBinding("Restore Screen", InputUtil.Type.KEYSYM, GLFW.GLFW_KEY_V, "UI Utils")); + + ClientTickEvents.END_CLIENT_TICK.register((client) -> { + com.ui_utils.features.PluginScanner.onTick(); + + while (restoreScreenKey.wasPressed()) { + if (SharedVariables.storedScreen != null && SharedVariables.storedScreenHandler != null && client.player != null) { + client.setScreen(SharedVariables.storedScreen); + client.player.currentScreenHandler = SharedVariables.storedScreenHandler; + } + } + }); + + if (!MinecraftClient.IS_SYSTEM_MAC) { + System.setProperty("java.awt.headless", "false"); + monospace = new Font(Font.MONOSPACED, Font.PLAIN, 10); + darkWhite = new Color(220, 220, 220); + } + } + + @SuppressWarnings("all") + public static void createText(MinecraftClient mc, DrawContext context, TextRenderer textRenderer) { + UITheme.drawPanel(context, 153, 4, 56, 24); + context.drawText(textRenderer, "Sync: " + mc.player.currentScreenHandler.syncId, 157, 7, UITheme.TEXT, false); + context.drawText(textRenderer, "Rev: " + mc.player.currentScreenHandler.getRevision(), 157, 17, UITheme.TEXT, false); + } + + public static void createWidgets(MinecraftClient mc, Screen screen) { + int x = 4; + int y = 4; + int w = 145; + int h = 18; + int spacing = 2; + + screen.addDrawableChild(CustomButtonWidget.create(x, y, w, Text.of("Close without packet"), (button) -> { + mc.setScreen(null); + })); + + screen.addDrawableChild(CustomButtonWidget.create(x, y + h + spacing, w, Text.of("Desync"), (button) -> { + if (mc.getNetworkHandler() != null && mc.player != null) { + mc.getNetworkHandler().sendPacket(new CloseHandledScreenC2SPacket(mc.player.currentScreenHandler.syncId)); + } + })); + + screen.addDrawableChild(CustomButtonWidget.create(x, y + (h + spacing) * 2, w, Text.of("Send packets: " + SharedVariables.sendUIPackets), (button) -> { + SharedVariables.sendUIPackets = !SharedVariables.sendUIPackets; + button.setMessage(Text.of("Send packets: " + SharedVariables.sendUIPackets)); + })); + + screen.addDrawableChild(CustomButtonWidget.create(x, y + (h + spacing) * 3, w, Text.of("Delay packets: " + SharedVariables.delayUIPackets), (button) -> { + SharedVariables.delayUIPackets = !SharedVariables.delayUIPackets; + button.setMessage(Text.of("Delay packets: " + SharedVariables.delayUIPackets)); + if (!SharedVariables.delayUIPackets && !SharedVariables.delayedUIPackets.isEmpty() && mc.getNetworkHandler() != null) { + for (Packet packet : SharedVariables.delayedUIPackets) { + mc.getNetworkHandler().sendPacket(packet); + } + if (mc.player != null) { + mc.player.sendMessage(Text.of("§7[§c*§7] §7Sent §c" + SharedVariables.delayedUIPackets.size() + " §7packets"), false); + } + SharedVariables.delayedUIPackets.clear(); + } + })); + + screen.addDrawableChild(CustomButtonWidget.create(x, y + (h + spacing) * 4, w, Text.of("Leave & Send Packets"), (button) -> { + SharedVariables.delayUIPackets = false; + if (mc.getNetworkHandler() != null) { + for (Packet packet : SharedVariables.delayedUIPackets) { + mc.getNetworkHandler().sendPacket(packet); + } + } + SharedVariables.delayedUIPackets.clear(); + mc.setScreen(null); + })); + + CustomButtonWidget fabricatePacketButton = CustomButtonWidget.create(x, y + (h + spacing) * 5, w, Text.of("Fabricate packet"), (button) -> { + JFrame frame = new JFrame("Choose Packet"); + frame.setBounds(0, 0, 450, 100); + frame.setResizable(false); + frame.setLocationRelativeTo(null); + frame.setLayout(null); + + JButton clickSlotButton = getPacketOptionButton("Click Slot"); + clickSlotButton.setBounds(100, 25, 110, 20); + clickSlotButton.addActionListener((event) -> { + frame.setVisible(false); + showClickSlotFrame(mc); + }); + + JButton buttonClickButton = getPacketOptionButton("Button Click"); + buttonClickButton.setBounds(250, 25, 110, 20); + buttonClickButton.addActionListener((event) -> { + frame.setVisible(false); + showButtonClickFrame(mc); + }); + + frame.add(clickSlotButton); + frame.add(buttonClickButton); + frame.setVisible(true); + }); + fabricatePacketButton.active = !MinecraftClient.IS_SYSTEM_MAC; + screen.addDrawableChild(fabricatePacketButton); + + screen.addDrawableChild(CustomButtonWidget.create(x, y + (h + spacing) * 6, w, Text.of("Copy GUI Title JSON"), (button) -> { + if (mc.currentScreen != null) { + Text title = mc.currentScreen.getTitle(); + String json = getTextAsJson(title, mc); + mc.keyboard.setClipboard(json); + if (mc.player != null) { + mc.player.sendMessage(Text.of("§7[§c*§7] §7Copied GUI title JSON to clipboard"), false); + } + } + })); + + int halfW = (w - spacing) / 2; + + screen.addDrawableChild(CustomButtonWidget.create(x, y + (h + spacing) * 7, halfW, Text.of("Save GUI"), (button) -> { + if (mc.player != null) { + SharedVariables.storedScreen = mc.currentScreen; + SharedVariables.storedScreenHandler = mc.player.currentScreenHandler; + SharedVariables.savedScreens.put("default", mc.currentScreen); + SharedVariables.savedScreenHandlers.put("default", mc.player.currentScreenHandler); + if (mc.player != null) { + mc.player.sendMessage(Text.of("§7[§c*§7] §7GUI saved"), false); + } + } + })); + + screen.addDrawableChild(CustomButtonWidget.create(x + halfW + spacing, y + (h + spacing) * 7, halfW, Text.of("Load GUI"), (button) -> { + if (SharedVariables.storedScreen != null && SharedVariables.storedScreenHandler != null && mc.player != null) { + mc.setScreen(SharedVariables.storedScreen); + mc.player.currentScreenHandler = SharedVariables.storedScreenHandler; + } + })); + + screen.addDrawableChild(CustomButtonWidget.create(x, y + (h + spacing) * 8, halfW, Text.of("Clear Queue"), (button) -> { + int count = SharedVariables.delayedUIPackets.size(); + SharedVariables.delayedUIPackets.clear(); + if (mc.player != null) { + mc.player.sendMessage(Text.of("§7[§c*§7] §7Cleared §c" + count + " §7packets"), false); + } + })); + + screen.addDrawableChild(CustomButtonWidget.create(x + halfW + spacing, y + (h + spacing) * 8, halfW, Text.of("Queue: 0"), (button) -> { + button.setMessage(Text.of("Queue: " + SharedVariables.delayedUIPackets.size())); + })); + + screen.addDrawableChild(CustomButtonWidget.create(x, y + (h + spacing) * 9, halfW, Text.of("Resync Inv"), (button) -> { + if (mc.getNetworkHandler() != null && mc.player != null) { + mc.player.currentScreenHandler.syncState(); + mc.player.sendMessage(Text.of("§7[§c*§7] §7Inventory resynced"), false); + } + })); + + screen.addDrawableChild(CustomButtonWidget.create(x + halfW + spacing, y + (h + spacing) * 9, halfW, Text.of("Disconnect"), (button) -> { + if (mc.getNetworkHandler() != null) { + mc.getNetworkHandler().getConnection().disconnect(Text.of("Disconnected")); + } + })); + + CustomButtonWidget spamButton = CustomButtonWidget.create(x + 32, y + (h + spacing) * 10, w - 64, Text.of("Spam (x" + SharedVariables.spamCount + ")"), (button) -> { + if (mc.getNetworkHandler() != null && !SharedVariables.delayedUIPackets.isEmpty()) { + int sent = 0; + for (int i = 0; i < SharedVariables.spamCount; i++) { + for (Packet packet : SharedVariables.delayedUIPackets) { + mc.getNetworkHandler().sendPacket(packet); + sent++; + } + } + if (mc.player != null) { + mc.player.sendMessage(Text.of("§7[§c*§7] §7Spammed §c" + sent + " §7packets"), false); + } + } else if (mc.player != null) { + mc.player.sendMessage(Text.of("§7[§c*§7] §cNo packets in queue"), false); + } + }); + + screen.addDrawableChild(CustomButtonWidget.create(x, y + (h + spacing) * 10, 30, Text.of("-"), (button) -> { + if (SharedVariables.spamCount > 1) { + SharedVariables.spamCount--; + spamButton.setMessage(Text.of("Spam (x" + SharedVariables.spamCount + ")")); + } + })); + + screen.addDrawableChild(spamButton); + + screen.addDrawableChild(CustomButtonWidget.create(x + w - 30, y + (h + spacing) * 10, 30, Text.of("+"), (button) -> { + if (SharedVariables.spamCount < 100) { + SharedVariables.spamCount++; + spamButton.setMessage(Text.of("Spam (x" + SharedVariables.spamCount + ")")); + } + })); + + screen.addDrawableChild(CustomButtonWidget.create(x, y + (h + spacing) * 11, halfW, Text.of("Send One"), (button) -> { + if (mc.getNetworkHandler() != null && !SharedVariables.delayedUIPackets.isEmpty()) { + Packet packet = SharedVariables.delayedUIPackets.remove(0); + mc.getNetworkHandler().sendPacket(packet); + if (mc.player != null) { + mc.player.sendMessage(Text.of("§7[§c*§7] §7Sent 1 packet §7(§c" + SharedVariables.delayedUIPackets.size() + "§7 left)"), false); + } + } else if (mc.player != null) { + mc.player.sendMessage(Text.of("§7[§c*§7] §cNo packets in queue"), false); + } + })); + + screen.addDrawableChild(CustomButtonWidget.create(x + halfW + spacing, y + (h + spacing) * 11, halfW, Text.of("Pop Last"), (button) -> { + if (!SharedVariables.delayedUIPackets.isEmpty()) { + SharedVariables.delayedUIPackets.remove(SharedVariables.delayedUIPackets.size() - 1); + if (mc.player != null) { + mc.player.sendMessage(Text.of("§7[§c*§7] §7Removed last packet §7(§c" + SharedVariables.delayedUIPackets.size() + "§7 left)"), false); + } + } else if (mc.player != null) { + mc.player.sendMessage(Text.of("§7[§c*§7] §cNo packets in queue"), false); + } + })); + } + + private static String getTextAsJson(Text text, MinecraftClient mc) { + try { + Class serializationClass = Class.forName("net.minecraft.text.Text$Serialization"); + java.lang.reflect.Method toJsonMethod = serializationClass.getMethod("toJsonString", Text.class, net.minecraft.registry.RegistryWrapper.WrapperLookup.class); + return (String) toJsonMethod.invoke(null, text, mc.world.getRegistryManager()); + } catch (ClassNotFoundException e) { + try { + Class serializerClass = Class.forName("net.minecraft.text.Text$Serializer"); + java.lang.reflect.Method toJsonMethod = serializerClass.getMethod("toJson", Text.class, net.minecraft.registry.RegistryWrapper.WrapperLookup.class); + Object result = toJsonMethod.invoke(null, text, mc.world.getRegistryManager()); + return result.toString(); + } catch (Exception ex) { + return text.getString(); + } + } catch (Exception e) { + return text.getString(); + } + } + + private static void showClickSlotFrame(MinecraftClient mc) { + JFrame clickSlotFrame = new JFrame("Click Slot Packet"); + clickSlotFrame.setBounds(0, 0, 450, 300); + clickSlotFrame.setResizable(false); + clickSlotFrame.setLocationRelativeTo(null); + clickSlotFrame.setLayout(null); + + JLabel syncIdLabel = new JLabel("Sync Id:"); + syncIdLabel.setFocusable(false); + syncIdLabel.setFont(monospace); + syncIdLabel.setBounds(25, 25, 100, 20); + + JLabel revisionLabel = new JLabel("Revision:"); + revisionLabel.setFocusable(false); + revisionLabel.setFont(monospace); + revisionLabel.setBounds(25, 50, 100, 20); + + JLabel slotLabel = new JLabel("Slot:"); + slotLabel.setFocusable(false); + slotLabel.setFont(monospace); + slotLabel.setBounds(25, 75, 100, 20); + + JLabel buttonLabel = new JLabel("Button:"); + buttonLabel.setFocusable(false); + buttonLabel.setFont(monospace); + buttonLabel.setBounds(25, 100, 100, 20); + + JLabel actionLabel = new JLabel("Action:"); + actionLabel.setFocusable(false); + actionLabel.setFont(monospace); + actionLabel.setBounds(25, 125, 100, 20); + + JLabel timesToSendLabel = new JLabel("Times to send:"); + timesToSendLabel.setFocusable(false); + timesToSendLabel.setFont(monospace); + timesToSendLabel.setBounds(25, 190, 100, 20); + + JTextField syncIdField = new JTextField(1); + syncIdField.setFont(monospace); + syncIdField.setBounds(125, 25, 100, 20); + + JTextField revisionField = new JTextField(1); + revisionField.setFont(monospace); + revisionField.setBounds(125, 50, 100, 20); + + JTextField slotField = new JTextField(1); + slotField.setFont(monospace); + slotField.setBounds(125, 75, 100, 20); + + JTextField buttonField = new JTextField(1); + buttonField.setFont(monospace); + buttonField.setBounds(125, 100, 100, 20); + + JComboBox actionField = new JComboBox<>(new Vector<>(ImmutableList.of( + "PICKUP", "QUICK_MOVE", "SWAP", "CLONE", "THROW", "QUICK_CRAFT", "PICKUP_ALL" + ))); + actionField.setFocusable(false); + actionField.setEditable(false); + actionField.setBorder(BorderFactory.createEmptyBorder()); + actionField.setBackground(darkWhite); + actionField.setFont(monospace); + actionField.setBounds(125, 125, 100, 20); + + JLabel statusLabel = new JLabel(); + statusLabel.setVisible(false); + statusLabel.setFocusable(false); + statusLabel.setFont(monospace); + statusLabel.setBounds(210, 150, 190, 20); + + JCheckBox delayBox = new JCheckBox("Delay"); + delayBox.setBounds(115, 150, 85, 20); + delayBox.setSelected(false); + delayBox.setFont(monospace); + delayBox.setFocusable(false); + + JTextField timesToSendField = new JTextField("1"); + timesToSendField.setFont(monospace); + timesToSendField.setBounds(125, 190, 100, 20); + + JButton sendButton = new JButton("Send"); + sendButton.setFocusable(false); + sendButton.setBounds(25, 150, 75, 20); + sendButton.setBorder(BorderFactory.createEtchedBorder()); + sendButton.setBackground(darkWhite); + sendButton.setFont(monospace); + sendButton.addActionListener((event0) -> { + if (isInteger(syncIdField.getText()) && isInteger(revisionField.getText()) && isInteger(slotField.getText()) && isInteger(buttonField.getText()) && isInteger(timesToSendField.getText()) && actionField.getSelectedItem() != null) { + int syncId = Integer.parseInt(syncIdField.getText()); + int revision = Integer.parseInt(revisionField.getText()); + short slot = Short.parseShort(slotField.getText()); + byte button0 = Byte.parseByte(buttonField.getText()); + SlotActionType action = stringToSlotActionType(actionField.getSelectedItem().toString()); + int timesToSend = Integer.parseInt(timesToSendField.getText()); + + if (action != null) { + ClickSlotC2SPacket packet = createClickSlotPacket(syncId, revision, slot, button0, action, ItemStack.EMPTY, new Int2ObjectArrayMap<>()); + try { + Runnable toRun = getFabricatePacketRunnable(mc, delayBox.isSelected(), packet); + for (int i = 0; i < timesToSend; i++) { + toRun.run(); + } + } catch (Exception e) { + statusLabel.setForeground(Color.RED.darker()); + statusLabel.setText("Must be connected to a server!"); + statusLabel.setVisible(true); + queueTask(() -> { statusLabel.setVisible(false); statusLabel.setText(""); }, 1500L); + return; + } + statusLabel.setVisible(true); + statusLabel.setForeground(Color.GREEN.darker()); + statusLabel.setText("Sent!"); + queueTask(() -> { statusLabel.setVisible(false); statusLabel.setText(""); }, 1500L); + } else { + statusLabel.setVisible(true); + statusLabel.setForeground(Color.RED.darker()); + statusLabel.setText("Invalid!"); + queueTask(() -> { statusLabel.setVisible(false); statusLabel.setText(""); }, 1500L); + } + } else { + statusLabel.setVisible(true); + statusLabel.setForeground(Color.RED.darker()); + statusLabel.setText("Invalid!"); + queueTask(() -> { statusLabel.setVisible(false); statusLabel.setText(""); }, 1500L); + } + }); + + clickSlotFrame.add(syncIdLabel); + clickSlotFrame.add(revisionLabel); + clickSlotFrame.add(slotLabel); + clickSlotFrame.add(buttonLabel); + clickSlotFrame.add(actionLabel); + clickSlotFrame.add(timesToSendLabel); + clickSlotFrame.add(syncIdField); + clickSlotFrame.add(revisionField); + clickSlotFrame.add(slotField); + clickSlotFrame.add(buttonField); + clickSlotFrame.add(actionField); + clickSlotFrame.add(sendButton); + clickSlotFrame.add(statusLabel); + clickSlotFrame.add(delayBox); + clickSlotFrame.add(timesToSendField); + clickSlotFrame.setVisible(true); + } + + private static void showButtonClickFrame(MinecraftClient mc) { + JFrame buttonClickFrame = new JFrame("Button Click Packet"); + buttonClickFrame.setBounds(0, 0, 450, 250); + buttonClickFrame.setResizable(false); + buttonClickFrame.setLocationRelativeTo(null); + buttonClickFrame.setLayout(null); + + JLabel syncIdLabel = new JLabel("Sync Id:"); + syncIdLabel.setFocusable(false); + syncIdLabel.setFont(monospace); + syncIdLabel.setBounds(25, 25, 100, 20); + + JLabel buttonIdLabel = new JLabel("Button Id:"); + buttonIdLabel.setFocusable(false); + buttonIdLabel.setFont(monospace); + buttonIdLabel.setBounds(25, 50, 100, 20); + + JTextField syncIdField = new JTextField(1); + syncIdField.setFont(monospace); + syncIdField.setBounds(125, 25, 100, 20); + + JTextField buttonIdField = new JTextField(1); + buttonIdField.setFont(monospace); + buttonIdField.setBounds(125, 50, 100, 20); + + JLabel statusLabel = new JLabel(); + statusLabel.setVisible(false); + statusLabel.setFocusable(false); + statusLabel.setFont(monospace); + statusLabel.setBounds(210, 95, 190, 20); + + JCheckBox delayBox = new JCheckBox("Delay"); + delayBox.setBounds(115, 95, 85, 20); + delayBox.setSelected(false); + delayBox.setFont(monospace); + delayBox.setFocusable(false); + + JLabel timesToSendLabel = new JLabel("Times to send:"); + timesToSendLabel.setFocusable(false); + timesToSendLabel.setFont(monospace); + timesToSendLabel.setBounds(25, 130, 100, 20); + + JTextField timesToSendField = new JTextField("1"); + timesToSendField.setFont(monospace); + timesToSendField.setBounds(125, 130, 100, 20); + + JButton sendButton = new JButton("Send"); + sendButton.setFocusable(false); + sendButton.setBounds(25, 95, 75, 20); + sendButton.setBorder(BorderFactory.createEtchedBorder()); + sendButton.setBackground(darkWhite); + sendButton.setFont(monospace); + sendButton.addActionListener((event0) -> { + if (isInteger(syncIdField.getText()) && isInteger(buttonIdField.getText()) && isInteger(timesToSendField.getText())) { + int syncId = Integer.parseInt(syncIdField.getText()); + int buttonId = Integer.parseInt(buttonIdField.getText()); + int timesToSend = Integer.parseInt(timesToSendField.getText()); + + ButtonClickC2SPacket packet = new ButtonClickC2SPacket(syncId, buttonId); + try { + Runnable toRun = getFabricatePacketRunnable(mc, delayBox.isSelected(), packet); + for (int i = 0; i < timesToSend; i++) { + toRun.run(); + } + } catch (Exception e) { + statusLabel.setVisible(true); + statusLabel.setForeground(Color.RED.darker()); + statusLabel.setText("Must be connected!"); + queueTask(() -> { statusLabel.setVisible(false); statusLabel.setText(""); }, 1500L); + return; + } + statusLabel.setVisible(true); + statusLabel.setForeground(Color.GREEN.darker()); + statusLabel.setText("Sent!"); + queueTask(() -> { statusLabel.setVisible(false); statusLabel.setText(""); }, 1500L); + } else { + statusLabel.setVisible(true); + statusLabel.setForeground(Color.RED.darker()); + statusLabel.setText("Invalid!"); + queueTask(() -> { statusLabel.setVisible(false); statusLabel.setText(""); }, 1500L); + } + }); + + buttonClickFrame.add(syncIdLabel); + buttonClickFrame.add(buttonIdLabel); + buttonClickFrame.add(syncIdField); + buttonClickFrame.add(timesToSendLabel); + buttonClickFrame.add(buttonIdField); + buttonClickFrame.add(sendButton); + buttonClickFrame.add(statusLabel); + buttonClickFrame.add(delayBox); + buttonClickFrame.add(timesToSendField); + buttonClickFrame.setVisible(true); + } + @NotNull + private static JButton getPacketOptionButton(String label) { + JButton button = new JButton(label); + button.setFocusable(false); + button.setBorder(BorderFactory.createEtchedBorder()); + button.setBackground(darkWhite); + button.setFont(monospace); + return button; + } + + @NotNull + private static Runnable getFabricatePacketRunnable(MinecraftClient mc, boolean delay, Packet packet) { + Runnable toRun; + if (delay) { + toRun = () -> { + if (mc.getNetworkHandler() != null) { + mc.getNetworkHandler().sendPacket(packet); + } + }; + } else { + toRun = () -> { + if (mc.getNetworkHandler() != null) { + mc.getNetworkHandler().sendPacket(packet); + } + ((ClientConnectionAccessor) mc.getNetworkHandler().getConnection()).getChannel().writeAndFlush(packet); + }; + } + return toRun; + } + + public static boolean isInteger(String string) { + try { + Integer.parseInt(string); + return true; + } catch (Exception e) { + return false; + } + } + + public static SlotActionType stringToSlotActionType(String string) { + return switch (string) { + case "PICKUP" -> SlotActionType.PICKUP; + case "QUICK_MOVE" -> SlotActionType.QUICK_MOVE; + case "SWAP" -> SlotActionType.SWAP; + case "CLONE" -> SlotActionType.CLONE; + case "THROW" -> SlotActionType.THROW; + case "QUICK_CRAFT" -> SlotActionType.QUICK_CRAFT; + case "PICKUP_ALL" -> SlotActionType.PICKUP_ALL; + default -> null; + }; + } + + public static void queueTask(Runnable runnable, long delayMs) { + Timer timer = new Timer(); + TimerTask task = new TimerTask() { + @Override + public void run() { + MinecraftClient.getInstance().send(runnable); + } + }; + timer.schedule(task, delayMs); + } + + public static String getModVersion(String modId) { + ModMetadata modMetadata = FabricLoader.getInstance().getModContainer(modId).isPresent() ? FabricLoader.getInstance().getModContainer(modId).get().getMetadata() : null; + return modMetadata != null ? modMetadata.getVersion().getFriendlyString() : "null"; + } + + @SuppressWarnings("unchecked") + public static ClickSlotC2SPacket createClickSlotPacket(int syncId, int revision, int slot, int button, SlotActionType action, ItemStack stack, Int2ObjectMap changedSlots) { + try { + for (Constructor constructor : ClickSlotC2SPacket.class.getDeclaredConstructors()) { + Class[] params = constructor.getParameterTypes(); + if (params.length == 7) { + constructor.setAccessible(true); + if (Int2ObjectMap.class.isAssignableFrom(params[5])) { + return (ClickSlotC2SPacket) constructor.newInstance(syncId, revision, slot, button, action, changedSlots, stack); + } else if (ItemStack.class.isAssignableFrom(params[5])) { + return (ClickSlotC2SPacket) constructor.newInstance(syncId, revision, slot, button, action, stack, changedSlots); + } + } + } + } catch (Exception e) { + LOGGER.error("Failed to create ClickSlotC2SPacket via reflection", e); + } + return null; + } +} diff --git a/src/main/java/com/ui_utils/SharedVariables.java b/src/main/java/com/ui_utils/SharedVariables.java index bab4363..1c06b28 100644 --- a/src/main/java/com/ui_utils/SharedVariables.java +++ b/src/main/java/com/ui_utils/SharedVariables.java @@ -5,6 +5,8 @@ import net.minecraft.screen.ScreenHandler; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; public class SharedVariables { public static boolean sendUIPackets = true; @@ -16,7 +18,13 @@ public class SharedVariables { public static Screen storedScreen = null; public static ScreenHandler storedScreenHandler = null; + public static Map savedScreens = new HashMap<>(); + public static Map savedScreenHandlers = new HashMap<>(); + public static boolean enabled = true; public static boolean bypassResourcePack = false; public static boolean resourcePackForceDeny = false; + + public static String commandPrefix = ","; + public static int spamCount = 1; } diff --git a/src/main/java/com/ui_utils/UpdateUtils.java b/src/main/java/com/ui_utils/UpdateUtils.java index 38d3bda..87cbec7 100644 --- a/src/main/java/com/ui_utils/UpdateUtils.java +++ b/src/main/java/com/ui_utils/UpdateUtils.java @@ -33,7 +33,7 @@ public static void checkForUpdates() { Callable task = () -> { HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://api.github.com/repos/Coderx-Gamer/ui-utils/releases/latest")) + .uri(URI.create("https://api.github.com/repos/FrannnnDev/ui-utils-advanced/releases/latest")) .header("Accept", "application/vnd.github.v3+json") .build(); @@ -72,12 +72,13 @@ public static void checkForUpdates() { public static void downloadUpdate() { MainClient.LOGGER.info("Opening download link..."); + String downloadUrl = "https://github.com/FrannnnDev/ui-utils-advanced/releases/latest"; try { if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { - Desktop.getDesktop().browse(new URI("https://ui-utils.com?ref=ingame&cv=" + currentVersion)); + Desktop.getDesktop().browse(new URI(downloadUrl)); } else { Runtime runtime = Runtime.getRuntime(); - runtime.exec(new String[]{"xdg-open", "https://ui-utils.com?ref=ingame&cv=" + currentVersion}); + runtime.exec(new String[]{"xdg-open", downloadUrl}); } } catch (IOException | URISyntaxException e) { MainClient.LOGGER.info(e.getLocalizedMessage(), Level.SEVERE); diff --git a/src/main/java/com/ui_utils/features/ClipUtils.java b/src/main/java/com/ui_utils/features/ClipUtils.java new file mode 100644 index 0000000..db690a0 --- /dev/null +++ b/src/main/java/com/ui_utils/features/ClipUtils.java @@ -0,0 +1,185 @@ +package com.ui_utils.features; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; +import net.minecraft.network.packet.c2s.play.VehicleMoveC2SPacket; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public class ClipUtils { + + private static Boolean hasHorizontalCollisionParam = null; + private static Boolean hasFromVehicleMethod = null; + + private static boolean hasHorizontalCollisionParam() { + if (hasHorizontalCollisionParam == null) { + try { + PlayerMoveC2SPacket.OnGroundOnly.class.getConstructor(boolean.class, boolean.class); + hasHorizontalCollisionParam = true; + } catch (NoSuchMethodException e) { + hasHorizontalCollisionParam = false; + } + } + return hasHorizontalCollisionParam; + } + + private static boolean hasFromVehicleMethod() { + if (hasFromVehicleMethod == null) { + try { + VehicleMoveC2SPacket.class.getMethod("fromVehicle", Entity.class); + hasFromVehicleMethod = true; + } catch (NoSuchMethodException e) { + hasFromVehicleMethod = false; + } + } + return hasFromVehicleMethod; + } + + private static VehicleMoveC2SPacket createVehiclePacket(Entity vehicle) { + try { + if (hasFromVehicleMethod()) { + Method method = VehicleMoveC2SPacket.class.getMethod("fromVehicle", Entity.class); + return (VehicleMoveC2SPacket) method.invoke(null, vehicle); + } else { + Constructor constructor = VehicleMoveC2SPacket.class.getConstructor(Entity.class); + return constructor.newInstance(vehicle); + } + } catch (Exception e) { + try { + Constructor constructor = VehicleMoveC2SPacket.class.getConstructor( + double.class, double.class, double.class, float.class, float.class + ); + return constructor.newInstance( + vehicle.getX(), vehicle.getY(), vehicle.getZ(), + vehicle.getYaw(), vehicle.getPitch() + ); + } catch (Exception e2) { + e2.printStackTrace(); + return null; + } + } + } + + private static PlayerMoveC2SPacket createOnGroundPacket(boolean onGround, boolean horizontalCollision) { + try { + if (hasHorizontalCollisionParam()) { + Constructor constructor = + PlayerMoveC2SPacket.OnGroundOnly.class.getConstructor(boolean.class, boolean.class); + return constructor.newInstance(onGround, horizontalCollision); + } else { + Constructor constructor = + PlayerMoveC2SPacket.OnGroundOnly.class.getConstructor(boolean.class); + return constructor.newInstance(onGround); + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static PlayerMoveC2SPacket createPositionPacket(double x, double y, double z, boolean onGround, boolean horizontalCollision) { + try { + if (hasHorizontalCollisionParam()) { + Constructor constructor = + PlayerMoveC2SPacket.PositionAndOnGround.class.getConstructor( + double.class, double.class, double.class, boolean.class, boolean.class + ); + return constructor.newInstance(x, y, z, onGround, horizontalCollision); + } else { + Constructor constructor = + PlayerMoveC2SPacket.PositionAndOnGround.class.getConstructor( + double.class, double.class, double.class, boolean.class + ); + return constructor.newInstance(x, y, z, onGround); + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public static void vClip(double blocks) { + MinecraftClient mc = MinecraftClient.getInstance(); + if (mc.player == null || mc.getNetworkHandler() == null) return; + + int packetsRequired = (int) Math.ceil(Math.abs(blocks / 10)); + if (packetsRequired > 20) { + packetsRequired = 1; + } + + if (mc.player.hasVehicle()) { + for (int i = 0; i < (packetsRequired - 1); i++) { + VehicleMoveC2SPacket packet = createVehiclePacket(mc.player.getVehicle()); + if (packet != null) mc.getNetworkHandler().sendPacket(packet); + } + mc.player.getVehicle().setPosition( + mc.player.getVehicle().getX(), + mc.player.getVehicle().getY() + blocks, + mc.player.getVehicle().getZ() + ); + VehicleMoveC2SPacket packet = createVehiclePacket(mc.player.getVehicle()); + if (packet != null) mc.getNetworkHandler().sendPacket(packet); + } else { + for (int i = 0; i < (packetsRequired - 1); i++) { + PlayerMoveC2SPacket packet = createOnGroundPacket(true, mc.player.horizontalCollision); + if (packet != null) mc.getNetworkHandler().sendPacket(packet); + } + PlayerMoveC2SPacket packet = createPositionPacket( + mc.player.getX(), + mc.player.getY() + blocks, + mc.player.getZ(), + true, + mc.player.horizontalCollision + ); + if (packet != null) mc.getNetworkHandler().sendPacket(packet); + mc.player.setPosition(mc.player.getX(), mc.player.getY() + blocks, mc.player.getZ()); + } + } + + public static void hClip(double blocks) { + MinecraftClient mc = MinecraftClient.getInstance(); + if (mc.player == null || mc.getNetworkHandler() == null) return; + + int packetsRequired = (int) Math.ceil(Math.abs(blocks / 10)); + if (packetsRequired > 20) { + packetsRequired = 1; + } + + float yaw = mc.player.getYaw(); + double radians = Math.toRadians(yaw); + double deltaX = -Math.sin(radians) * blocks; + double deltaZ = Math.cos(radians) * blocks; + + if (mc.player.hasVehicle()) { + for (int i = 0; i < (packetsRequired - 1); i++) { + VehicleMoveC2SPacket packet = createVehiclePacket(mc.player.getVehicle()); + if (packet != null) mc.getNetworkHandler().sendPacket(packet); + } + mc.player.getVehicle().setPosition( + mc.player.getVehicle().getX() + deltaX, + mc.player.getVehicle().getY(), + mc.player.getVehicle().getZ() + deltaZ + ); + VehicleMoveC2SPacket packet = createVehiclePacket(mc.player.getVehicle()); + if (packet != null) mc.getNetworkHandler().sendPacket(packet); + } else { + for (int i = 0; i < (packetsRequired - 1); i++) { + PlayerMoveC2SPacket packet = createOnGroundPacket(true, mc.player.horizontalCollision); + if (packet != null) mc.getNetworkHandler().sendPacket(packet); + } + PlayerMoveC2SPacket packet = createPositionPacket( + mc.player.getX() + deltaX, + mc.player.getY(), + mc.player.getZ() + deltaZ, + true, + mc.player.horizontalCollision + ); + if (packet != null) mc.getNetworkHandler().sendPacket(packet); + mc.player.setPosition(mc.player.getX() + deltaX, mc.player.getY(), mc.player.getZ() + deltaZ); + } + } +} diff --git a/src/main/java/com/ui_utils/features/CommandSystem.java b/src/main/java/com/ui_utils/features/CommandSystem.java new file mode 100644 index 0000000..9903ac3 --- /dev/null +++ b/src/main/java/com/ui_utils/features/CommandSystem.java @@ -0,0 +1,377 @@ +package com.ui_utils.features; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.gui.screen.multiplayer.ConnectScreen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.network.ServerAddress; +import net.minecraft.client.network.ServerInfo; +import net.minecraft.client.session.Session; +import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket; +import net.minecraft.text.Text; +import com.ui_utils.SharedVariables; + +import java.util.*; + +public class CommandSystem { + private static final String P = "§7[§c*§7] "; + + private static final Map commands = new LinkedHashMap<>(); + private static final Map manuals = new LinkedHashMap<>(); + + static { + registerCommand("help", CommandSystem::helpCommand, "§7Lists all commands or shows help for one\n§cUsage: §7,help [command]"); + registerCommand("man", CommandSystem::manCommand, "§7Shows detailed manual for a command\n§cUsage: §7,man "); + registerCommand("toggleuiutils", CommandSystem::toggleCommand, "§7Toggles UI-Utils overlay on/off\n§cUsage: §7,toggleuiutils"); + registerCommand("echo", CommandSystem::echoCommand, "§7Prints text to chat (client-side)\n§cUsage: §7,echo "); + registerCommand("math", CommandSystem::mathCommand, "§7Calculates math expression\n§cSupports: §7+ - * / ^ ()\n§cUsage: §7,math \n§cExample: §7,math 2+2*3"); + registerCommand("close", CommandSystem::closeCommand, "§7Closes current screen without packet\n§cUsage: §7,close"); + registerCommand("desync", CommandSystem::desyncCommand, "§7Sends close packet but keeps GUI open\n§cUsage: §7,desync"); + registerCommand("chat", CommandSystem::chatCommand, "§7Sends message/command to server\n§cUsage: §7,chat \n§cExample: §7,chat /spawn"); + registerCommand("joinserver", CommandSystem::joinServerCommand, "§7Connects to another server\n§cUsage: §7,joinserver \n§cExample: §7,joinserver mc.hypixel.net"); + registerCommand("screen", CommandSystem::screenCommand, "§7Manages saved screens\n§cActions:\n§7 save - Save current screen\n§7 load - Load saved screen\n§7 list - List all saved slots\n§7 info - Show screen info"); + registerCommand("account", CommandSystem::accountCommand, "§7View/change session info\n§cActions:\n§7 dump - Show current username/UUID\n§7 set username - Change username\n§7 set uuid - Change UUID\n§cNote: §7Changes are temporary until restart"); + registerCommand("prefix", CommandSystem::prefixCommand, "§7View/change command prefix\n§cUsage: §7,prefix [new]\n§cExample: §7,prefix ."); + registerCommand("vclip", CommandSystem::vclipCommand, "§7Teleports vertically (client-side)\n§cUsage: §7,vclip \n§cExample: §7,vclip 5 (up), ,vclip -3 (down)"); + registerCommand("hclip", CommandSystem::hclipCommand, "§7Teleports horizontally in look direction\n§cUsage: §7,hclip \n§cExample: §7,hclip 10"); + registerCommand("plugins", CommandSystem::pluginsCommand, "§7Scans server for plugins via tab-complete\n§cUsage: §7,plugins\n§4Red §7= Anticheat/exploit related"); + } + + public static void registerCommand(String name, CommandHandler handler, String manual) { + commands.put(name.toLowerCase(), handler); + manuals.put(name.toLowerCase(), manual); + } + + public static String execute(String input) { + if (input == null || input.trim().isEmpty()) { + return P + "§cNo command"; + } + + String[] parts = input.trim().split("\\s+", 2); + String commandName = parts[0].toLowerCase(); + String args = parts.length > 1 ? parts[1] : ""; + + CommandHandler handler = commands.get(commandName); + if (handler == null) { + return P + "§cUnknown: §7" + commandName; + } + + try { + return handler.execute(args); + } catch (Exception e) { + return P + "§cError: §7" + e.getMessage(); + } + } + + private static String helpCommand(String args) { + if (!args.isEmpty()) { + return manCommand(args); + } + StringBuilder sb = new StringBuilder(); + sb.append(P).append("Commands: "); + int i = 0; + for (String cmd : commands.keySet()) { + sb.append("§c").append(cmd); + if (i < commands.size() - 1) { + sb.append("§7, "); + } + i++; + } + return sb.toString(); + } + + private static String manCommand(String args) { + if (args.isEmpty()) { + return P + "§cUsage: man "; + } + String manual = manuals.get(args.toLowerCase()); + if (manual == null) { + return P + "§cNo manual for: §7" + args; + } + return P + manual.replace("\n", "\n" + P); + } + + private static String toggleCommand(String args) { + SharedVariables.enabled = !SharedVariables.enabled; + return "§7UI-Utils: " + (SharedVariables.enabled ? "§aON" : "§cOFF"); + } + + private static String echoCommand(String args) { + return args.isEmpty() ? "" : P + args; + } + + private static String mathCommand(String args) { + if (args.isEmpty()) { + return P + "§cUsage: math "; + } + try { + double result = evaluateExpression(args); + return P + "Result: §c" + result; + } catch (Exception e) { + return P + "§cInvalid expression"; + } + } + + private static String closeCommand(String args) { + MinecraftClient mc = MinecraftClient.getInstance(); + mc.execute(() -> mc.setScreen(null)); + return P + "Screen closed"; + } + + private static String desyncCommand(String args) { + MinecraftClient mc = MinecraftClient.getInstance(); + if (mc.getNetworkHandler() == null || mc.player == null) { + return P + "§cNot connected"; + } + int syncId = mc.player.currentScreenHandler.syncId; + mc.getNetworkHandler().sendPacket(new CloseHandledScreenC2SPacket(syncId)); + return P + "Desync sent §7(syncId: §c" + syncId + "§7)"; + } + + private static String chatCommand(String args) { + if (args.isEmpty()) { + return P + "§cUsage: chat "; + } + MinecraftClient mc = MinecraftClient.getInstance(); + if (mc.player == null) { + return P + "§cNot in game"; + } + mc.execute(() -> { + if (args.startsWith("/")) { + mc.player.networkHandler.sendChatCommand(args.substring(1)); + } else { + mc.player.networkHandler.sendChatMessage(args); + } + }); + return P + "Sent: §c" + args; + } + + private static String joinServerCommand(String args) { + if (args.isEmpty()) { + return P + "§cUsage: joinserver "; + } + MinecraftClient mc = MinecraftClient.getInstance(); + String serverIp = args.split("\\s+")[0]; + ServerAddress address = ServerAddress.parse(serverIp); + ServerInfo serverInfo = new ServerInfo("Server", serverIp, ServerInfo.ServerType.OTHER); + + mc.execute(() -> { + try { + if (mc.world != null) { + try { + mc.world.getClass().getMethod("disconnect").invoke(mc.world); + } catch (NoSuchMethodException e) { + mc.world.getClass().getMethod("disconnect", Text.class).invoke(mc.world, Text.empty()); + } + } + try { + mc.getClass().getMethod("disconnect").invoke(mc); + } catch (NoSuchMethodException e) { + mc.getClass().getMethod("disconnect", net.minecraft.client.gui.screen.Screen.class, boolean.class) + .invoke(mc, new TitleScreen(), false); + } + } catch (Exception e) { + e.printStackTrace(); + } + ConnectScreen.connect(new MultiplayerScreen(new TitleScreen()), mc, address, serverInfo, false, null); + }); + return P + "Joining: §c" + serverIp; + } + + private static String screenCommand(String args) { + String[] parts = args.split("\\s+", 2); + if (parts.length == 0 || parts[0].isEmpty()) { + return P + "§cUsage: screen [slot]"; + } + String action = parts[0].toLowerCase(); + String slot = parts.length > 1 ? parts[1] : ""; + MinecraftClient mc = MinecraftClient.getInstance(); + + return switch (action) { + case "save" -> { + if (slot.isEmpty()) yield P + "§cUsage: screen save "; + if (mc.currentScreen == null || mc.player == null) yield P + "§cNo screen to save"; + SharedVariables.savedScreens.put(slot, mc.currentScreen); + SharedVariables.savedScreenHandlers.put(slot, mc.player.currentScreenHandler); + yield P + "Saved to: §c" + slot; + } + case "load" -> { + if (slot.isEmpty()) yield P + "§cUsage: screen load "; + Screen screen = SharedVariables.savedScreens.get(slot); + if (screen == null) yield P + "§cNo screen in slot: §7" + slot; + mc.execute(() -> { + mc.setScreen(screen); + if (mc.player != null && SharedVariables.savedScreenHandlers.containsKey(slot)) { + mc.player.currentScreenHandler = SharedVariables.savedScreenHandlers.get(slot); + } + }); + yield P + "Loaded: §c" + slot; + } + case "list" -> { + if (SharedVariables.savedScreens.isEmpty()) yield P + "No saved screens"; + yield P + "Slots: §c" + String.join("§7, §c", SharedVariables.savedScreens.keySet()); + } + case "info" -> { + if (slot.isEmpty()) yield P + "§cUsage: screen info "; + Screen screen = SharedVariables.savedScreens.get(slot); + if (screen == null) yield P + "§cNo screen in slot: §7" + slot; + yield P + "Slot: §c" + slot + " §7| Screen: §c" + screen.getClass().getSimpleName(); + } + default -> P + "§cUnknown action"; + }; + } + + private static String accountCommand(String args) { + String[] parts = args.split("\\s+", 3); + if (parts.length == 0 || parts[0].isEmpty()) { + return P + "§cUsage: account [args]"; + } + String action = parts[0].toLowerCase(); + MinecraftClient mc = MinecraftClient.getInstance(); + + return switch (action) { + case "dump" -> { + Session session = mc.getSession(); + yield P + "Username: §c" + session.getUsername() + "\n" + P + "UUID: §c" + session.getUuidOrNull(); + } + case "set" -> { + if (parts.length < 3) yield P + "§cUsage: account set "; + String type = parts[1].toLowerCase(); + String value = parts[2]; + + try { + Session oldSession = mc.getSession(); + Session newSession; + + if (type.equals("username")) { + newSession = new Session(value, oldSession.getUuidOrNull(), oldSession.getAccessToken(), java.util.Optional.empty(), java.util.Optional.empty(), Session.AccountType.MOJANG); + } else if (type.equals("uuid")) { + java.util.UUID uuid = java.util.UUID.fromString(value); + newSession = new Session(oldSession.getUsername(), uuid, oldSession.getAccessToken(), java.util.Optional.empty(), java.util.Optional.empty(), Session.AccountType.MOJANG); + } else { + yield P + "§cUnknown type: §7" + type + " §c(use username or uuid)"; + } + + java.lang.reflect.Field sessionField = MinecraftClient.class.getDeclaredField("session"); + sessionField.setAccessible(true); + sessionField.set(mc, newSession); + + yield P + "Set §c" + type + " §7= §c" + value; + } catch (IllegalArgumentException e) { + yield P + "§cInvalid UUID format"; + } catch (Exception e) { + yield P + "§cFailed to set " + type + ": §7" + e.getMessage(); + } + } + default -> P + "§cUnknown action: §7" + action; + }; + } + + private static String prefixCommand(String args) { + if (args.isEmpty()) { + return P + "Current prefix: §c" + SharedVariables.commandPrefix; + } + String newPrefix = args.split("\\s+")[0]; + SharedVariables.commandPrefix = newPrefix; + return P + "Prefix changed to: §c" + newPrefix; + } + + private static String vclipCommand(String args) { + if (args.isEmpty()) { + return P + "§cUsage: vclip "; + } + try { + double blocks = Double.parseDouble(args.split("\\s+")[0]); + ClipUtils.vClip(blocks); + return P + "VClip: §c" + blocks + " §7blocks"; + } catch (NumberFormatException e) { + return P + "§cInvalid number"; + } + } + + private static String hclipCommand(String args) { + if (args.isEmpty()) { + return P + "§cUsage: hclip "; + } + try { + double blocks = Double.parseDouble(args.split("\\s+")[0]); + ClipUtils.hClip(blocks); + return P + "HClip: §c" + blocks + " §7blocks"; + } catch (NumberFormatException e) { + return P + "§cInvalid number"; + } + } + + private static String pluginsCommand(String args) { + PluginScanner.startScan(); + return P + "Scanning plugins..."; + } + + private static double evaluateExpression(String expression) { + expression = expression.replaceAll("\\s+", ""); + return parseExpression(expression, new int[]{0}); + } + + private static double parseExpression(String expr, int[] pos) { + double result = parseTerm(expr, pos); + while (pos[0] < expr.length()) { + char op = expr.charAt(pos[0]); + if (op == '+') { pos[0]++; result += parseTerm(expr, pos); } + else if (op == '-') { pos[0]++; result -= parseTerm(expr, pos); } + else break; + } + return result; + } + + private static double parseTerm(String expr, int[] pos) { + double result = parseFactor(expr, pos); + while (pos[0] < expr.length()) { + char op = expr.charAt(pos[0]); + if (op == '*') { pos[0]++; result *= parseFactor(expr, pos); } + else if (op == '/') { pos[0]++; result /= parseFactor(expr, pos); } + else break; + } + return result; + } + + private static double parseFactor(String expr, int[] pos) { + return parsePower(expr, pos); + } + + private static double parsePower(String expr, int[] pos) { + double base = parseUnary(expr, pos); + if (pos[0] < expr.length() && expr.charAt(pos[0]) == '^') { + pos[0]++; + return Math.pow(base, parsePower(expr, pos)); + } + return base; + } + + private static double parseUnary(String expr, int[] pos) { + if (pos[0] < expr.length() && expr.charAt(pos[0]) == '-') { + pos[0]++; + return -parseUnary(expr, pos); + } + return parsePrimary(expr, pos); + } + + private static double parsePrimary(String expr, int[] pos) { + if (pos[0] < expr.length() && expr.charAt(pos[0]) == '(') { + pos[0]++; + double result = parseExpression(expr, pos); + if (pos[0] < expr.length() && expr.charAt(pos[0]) == ')') pos[0]++; + return result; + } + StringBuilder sb = new StringBuilder(); + while (pos[0] < expr.length() && (Character.isDigit(expr.charAt(pos[0])) || expr.charAt(pos[0]) == '.')) { + sb.append(expr.charAt(pos[0]++)); + } + return Double.parseDouble(sb.toString()); + } + + @FunctionalInterface + public interface CommandHandler { + String execute(String args); + } +} diff --git a/src/main/java/com/ui_utils/features/PluginScanner.java b/src/main/java/com/ui_utils/features/PluginScanner.java new file mode 100644 index 0000000..cad18d8 --- /dev/null +++ b/src/main/java/com/ui_utils/features/PluginScanner.java @@ -0,0 +1,121 @@ +package com.ui_utils.features; + +import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.Suggestions; +import net.minecraft.client.MinecraftClient; +import net.minecraft.network.packet.c2s.play.RequestCommandCompletionsC2SPacket; +import net.minecraft.network.packet.s2c.play.CommandSuggestionsS2CPacket; +import net.minecraft.text.Text; + +import java.util.*; + +public class PluginScanner { + private static final String P = "§7[§c*§7] "; + + private static final Set ANTICHEAT_LIST = Set.of( + "nocheatplus", "negativity", "warden", "horizon", "illegalstack", + "coreprotect", "exploitsx", "vulcan", "abc", "spartan", "kauri", + "anticheatreloaded", "witherac", "godseye", "matrix", "wraith", + "antixrayheuristics", "grimac", "themis", "foxaddition", "guardianac", + "ggintegrity", "lightanticheat", "anarchyexploitfixes" + ); + + private static final Random RANDOM = new Random(); + private static boolean scanning = false; + private static int ticksWaiting = 0; + private static final List foundPlugins = new ArrayList<>(); + + public static void startScan() { + MinecraftClient mc = MinecraftClient.getInstance(); + if (mc.getNetworkHandler() == null || mc.player == null) { + return; + } + + if (scanning) return; + + foundPlugins.clear(); + scanning = true; + ticksWaiting = 0; + + mc.getNetworkHandler().sendPacket(new RequestCommandCompletionsC2SPacket(RANDOM.nextInt(200), "ver ")); + } + + public static void onTick() { + if (!scanning) return; + + ticksWaiting++; + if (ticksWaiting >= 60) { + printResults(); + } + } + + public static void onCommandSuggestions(CommandSuggestionsS2CPacket packet) { + if (!scanning) return; + + scanning = false; + + try { + Suggestions suggestions = packet.getSuggestions(); + MinecraftClient mc = MinecraftClient.getInstance(); + + if (suggestions.isEmpty()) { + if (mc.player != null) { + mc.player.sendMessage(Text.of(P + "§cNo plugins found or blocked"), false); + } + return; + } + + for (Suggestion suggestion : suggestions.getList()) { + String pluginName = suggestion.getText().trim(); + if (!pluginName.isEmpty() && !foundPlugins.contains(pluginName.toLowerCase())) { + foundPlugins.add(pluginName); + } + } + + printResults(); + } catch (Exception e) { + scanning = false; + } + } + + private static void printResults() { + scanning = false; + ticksWaiting = 0; + + MinecraftClient mc = MinecraftClient.getInstance(); + if (mc.player == null) return; + + if (foundPlugins.isEmpty()) { + mc.player.sendMessage(Text.of(P + "§cNo plugins found"), false); + return; + } + + foundPlugins.sort(String.CASE_INSENSITIVE_ORDER); + + StringBuilder sb = new StringBuilder(); + sb.append(P).append("Plugins §7(§c").append(foundPlugins.size()).append("§7): "); + + for (int i = 0; i < foundPlugins.size(); i++) { + String plugin = foundPlugins.get(i); + if (ANTICHEAT_LIST.contains(plugin.toLowerCase()) || + plugin.toLowerCase().contains("exploit") || + plugin.toLowerCase().contains("cheat") || + plugin.toLowerCase().contains("illegal")) { + sb.append("§4"); + } else { + sb.append("§c"); + } + sb.append(plugin); + if (i < foundPlugins.size() - 1) { + sb.append("§7, "); + } + } + + mc.player.sendMessage(Text.of(sb.toString()), false); + foundPlugins.clear(); + } + + public static boolean isScanning() { + return scanning; + } +} diff --git a/src/main/java/com/ui_utils/gui/ChatTextFieldWidget.java b/src/main/java/com/ui_utils/gui/ChatTextFieldWidget.java new file mode 100644 index 0000000..e09ebd8 --- /dev/null +++ b/src/main/java/com/ui_utils/gui/ChatTextFieldWidget.java @@ -0,0 +1,41 @@ +package com.ui_utils.gui; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; +import com.ui_utils.SharedVariables; + +import java.util.regex.Pattern; + +public class ChatTextFieldWidget extends CustomTextFieldWidget { + private static final MinecraftClient mc = MinecraftClient.getInstance(); + + public ChatTextFieldWidget(TextRenderer textRenderer, int x, int y, int width, int height, Text text) { + super(textRenderer, x, y, width, height, text); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (keyCode == GLFW.GLFW_KEY_ENTER) { + String toggleCmd = SharedVariables.commandPrefix + "toggleuiutils"; + if (this.getText().equalsIgnoreCase(toggleCmd)) { + SharedVariables.enabled = !SharedVariables.enabled; + if (mc.player != null) { + mc.player.sendMessage(Text.of("§7UI-Utils: " + (SharedVariables.enabled ? "ON" : "OFF")), false); + } + this.setText(""); + return false; + } + if (mc.getNetworkHandler() != null) { + if (this.getText().startsWith("/")) { + mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); + } else { + mc.getNetworkHandler().sendChatMessage(this.getText()); + } + } + this.setText(""); + } + return super.keyPressed(keyCode, scanCode, modifiers); + } +} diff --git a/src/main/java/com/ui_utils/gui/CustomButtonWidget.java b/src/main/java/com/ui_utils/gui/CustomButtonWidget.java new file mode 100644 index 0000000..45f10aa --- /dev/null +++ b/src/main/java/com/ui_utils/gui/CustomButtonWidget.java @@ -0,0 +1,38 @@ +package com.ui_utils.gui; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.Text; + +public class CustomButtonWidget extends ButtonWidget { + public CustomButtonWidget(int x, int y, int width, int height, Text message, PressAction onPress) { + super(x, y, width, height, message, onPress, DEFAULT_NARRATION_SUPPLIER); + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + boolean hovered = this.isHovered(); + int bg = hovered ? UITheme.BACKGROUND_HOVER : UITheme.BACKGROUND; + int border = hovered ? UITheme.BORDER_HOVER : UITheme.BORDER; + + context.fill(this.getX() - 1, this.getY() - 1, this.getX() + this.width + 1, this.getY() + this.height + 1, UITheme.BORDER_OUTER); + context.fill(this.getX(), this.getY(), this.getX() + this.width, this.getY() + this.height, bg); + UITheme.drawBorder(context, this.getX(), this.getY(), this.width, this.height, border); + + if (hovered) { + context.fill(this.getX(), this.getY() + this.height - 2, this.getX() + this.width, this.getY() + this.height, UITheme.BORDER_ACCENT); + } + + int textColor = this.active ? UITheme.TEXT : UITheme.TEXT_DIM; + context.drawCenteredTextWithShadow(MinecraftClient.getInstance().textRenderer, this.getMessage(), this.getX() + this.width / 2, this.getY() + (this.height - 8) / 2, textColor); + } + + public static CustomButtonWidget create(int x, int y, int width, Text message, PressAction onPress) { + return new CustomButtonWidget(x, y, width, 16, message, onPress); + } + + public static CustomButtonWidget createSmall(int x, int y, int width, Text message, PressAction onPress) { + return new CustomButtonWidget(x, y, width, 14, message, onPress); + } +} diff --git a/src/main/java/com/ui_utils/gui/CustomTextFieldWidget.java b/src/main/java/com/ui_utils/gui/CustomTextFieldWidget.java new file mode 100644 index 0000000..0f46cdb --- /dev/null +++ b/src/main/java/com/ui_utils/gui/CustomTextFieldWidget.java @@ -0,0 +1,42 @@ +package com.ui_utils.gui; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.text.Text; + +public class CustomTextFieldWidget extends TextFieldWidget { + private String placeholder; + + public CustomTextFieldWidget(TextRenderer textRenderer, int x, int y, int width, int height, Text text) { + super(textRenderer, x, y, width, height, text); + this.setDrawsBackground(false); + this.placeholder = text.getString(); + } + + @Override + public void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + UITheme.drawInputBackground(context, this.getX() - 2, this.getY() - 2, this.width + 4, this.height + 4, this.isFocused()); + super.renderWidget(context, mouseX, mouseY, delta); + if (this.getText().isEmpty() && !this.isFocused() && this.placeholder != null && !this.placeholder.isEmpty()) { + context.drawText(MinecraftClient.getInstance().textRenderer, this.placeholder, this.getX() + 2, this.getY() + 2, UITheme.TEXT_DIM, false); + } + } + + public void setPlaceholder(String placeholder) { + this.placeholder = placeholder; + } + + public static CustomTextFieldWidget create(TextRenderer textRenderer, int x, int y, int width, Text placeholder) { + CustomTextFieldWidget field = new CustomTextFieldWidget(textRenderer, x, y, width, 12, placeholder); + field.setMaxLength(255); + return field; + } + + public static CustomTextFieldWidget create(TextRenderer textRenderer, int x, int y, int width, int height, Text placeholder) { + CustomTextFieldWidget field = new CustomTextFieldWidget(textRenderer, x, y, width, height, placeholder); + field.setMaxLength(255); + return field; + } +} diff --git a/src/main/java/com/ui_utils/gui/UITheme.java b/src/main/java/com/ui_utils/gui/UITheme.java new file mode 100644 index 0000000..09752de --- /dev/null +++ b/src/main/java/com/ui_utils/gui/UITheme.java @@ -0,0 +1,51 @@ +package com.ui_utils.gui; + +import net.minecraft.client.gui.DrawContext; + +public class UITheme { + public static final int BACKGROUND = 0xE6101010; + public static final int BACKGROUND_HOVER = 0xF01a1a1a; + public static final int BORDER = 0xFF3f3f46; + public static final int BORDER_HOVER = 0xFF52525b; + public static final int BORDER_ACCENT = 0xFF6366f1; + public static final int BORDER_OUTER = 0xFF18181b; + public static final int TEXT = 0xFFfafafa; + public static final int TEXT_DIM = 0xFF71717a; + public static final int INPUT_BG = 0xE6141414; + public static final int INPUT_BORDER = 0xFF3f3f46; + public static final int INPUT_BORDER_FOCUS = 0xFF6366f1; + + public static void drawPanel(DrawContext context, int x, int y, int width, int height) { + context.fill(x - 1, y - 1, x + width + 1, y + height + 1, BORDER_OUTER); + context.fill(x, y, x + width, y + height, BACKGROUND); + drawBorder(context, x, y, width, height, BORDER); + } + + public static void drawPanelWithAccent(DrawContext context, int x, int y, int width, int height) { + context.fill(x - 1, y - 1, x + width + 1, y + height + 1, BORDER_OUTER); + context.fill(x, y, x + width, y + height, BACKGROUND); + context.fill(x, y, x + 2, y + height, BORDER_ACCENT); + drawBorder(context, x, y, width, height, BORDER); + } + + public static void drawBorder(DrawContext context, int x, int y, int width, int height, int color) { + context.fill(x, y, x + width, y + 1, color); + context.fill(x, y + height - 1, x + width, y + height, color); + context.fill(x, y, x + 1, y + height, color); + context.fill(x + width - 1, y, x + width, y + height, color); + } + + public static void drawDoubleBorder(DrawContext context, int x, int y, int width, int height, int innerColor, int outerColor) { + context.fill(x - 1, y - 1, x + width + 1, y, outerColor); + context.fill(x - 1, y + height, x + width + 1, y + height + 1, outerColor); + context.fill(x - 1, y - 1, x, y + height + 1, outerColor); + context.fill(x + width, y - 1, x + width + 1, y + height + 1, outerColor); + drawBorder(context, x, y, width, height, innerColor); + } + + public static void drawInputBackground(DrawContext context, int x, int y, int width, int height, boolean focused) { + context.fill(x - 1, y - 1, x + width + 1, y + height + 1, BORDER_OUTER); + context.fill(x, y, x + width, y + height, INPUT_BG); + drawBorder(context, x, y, width, height, focused ? INPUT_BORDER_FOCUS : INPUT_BORDER); + } +} diff --git a/src/main/java/com/ui_utils/gui/UpdateScreen.java b/src/main/java/com/ui_utils/gui/UpdateScreen.java index ec771a2..99dbcda 100644 --- a/src/main/java/com/ui_utils/gui/UpdateScreen.java +++ b/src/main/java/com/ui_utils/gui/UpdateScreen.java @@ -1,12 +1,10 @@ package com.ui_utils.gui; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.TextWidget; import net.minecraft.text.Text; public class UpdateScreen extends Screen { - public UpdateScreen(Text title) { super(title); } @@ -14,23 +12,34 @@ public UpdateScreen(Text title) { @Override protected void init() { super.init(); - Text message1 = Text.of("In order to update UI-Utils, first quit the game then"); - Text message2 = Text.of("delete the old UI-Utils jar file, and replace it with the new one you got on the website."); int centerX = this.width / 2; + int btnWidth = 70; + int spacing = 8; - this.addDrawableChild(new TextWidget(centerX - textRenderer.getWidth(message1) / 2, 80, textRenderer.getWidth(message1), 20, message1, this.textRenderer)); - this.addDrawableChild(new TextWidget(centerX - textRenderer.getWidth(message2) / 2, 95, textRenderer.getWidth(message2), 20, Text.of(message2), this.textRenderer)); - - int quitX = centerX - 85; - int backX = centerX + 5; - - this.addDrawableChild(ButtonWidget.builder(Text.of("Quit"), (button) -> { + this.addDrawableChild(CustomButtonWidget.create(centerX - btnWidth - spacing / 2, 130, btnWidth, Text.of("Quit"), (button) -> { this.client.stop(); - }).width(80).position(quitX, 145).build()); + })); - this.addDrawableChild(ButtonWidget.builder(Text.of("Back"), (button) -> { + this.addDrawableChild(CustomButtonWidget.create(centerX + spacing / 2, 130, btnWidth, Text.of("Back"), (button) -> { this.client.setScreen(null); - }).width(80).position(backX, 145).build()); + })); } + @Override + public void render(DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + int centerX = this.width / 2; + + UITheme.drawPanelWithAccent(context, centerX - 170, 60, 340, 100); + + String line1 = "To update UI-Utils:"; + String line2 = "1. Quit the game"; + String line3 = "2. Delete old UI-Utils jar"; + String line4 = "3. Add the new jar to mods folder"; + + context.drawCenteredTextWithShadow(this.textRenderer, line1, centerX, 72, UITheme.TEXT); + context.drawText(this.textRenderer, line2, centerX - 155, 90, UITheme.TEXT_DIM, false); + context.drawText(this.textRenderer, line3, centerX - 155, 103, UITheme.TEXT_DIM, false); + context.drawText(this.textRenderer, line4, centerX - 155, 116, UITheme.TEXT_DIM, false); + } } \ No newline at end of file diff --git a/src/main/java/com/ui_utils/mixin/BookEditScreenMixin.java b/src/main/java/com/ui_utils/mixin/BookEditScreenMixin.java index 1a78367..6681001 100644 --- a/src/main/java/com/ui_utils/mixin/BookEditScreenMixin.java +++ b/src/main/java/com/ui_utils/mixin/BookEditScreenMixin.java @@ -1,11 +1,10 @@ package com.ui_utils.mixin; +import com.ui_utils.gui.ChatTextFieldWidget; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.BookEditScreen; -import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -14,13 +13,12 @@ import com.ui_utils.MainClient; import com.ui_utils.SharedVariables; -import java.util.regex.Pattern; - @Mixin(BookEditScreen.class) public class BookEditScreenMixin extends Screen { protected BookEditScreenMixin(Text title) { super(title); } + @Unique private static final MinecraftClient mc = MinecraftClient.getInstance(); @@ -29,38 +27,10 @@ public void init(CallbackInfo ci) { if (SharedVariables.enabled) { MainClient.createWidgets(mc, this); - // create chat box - TextFieldWidget addressField = new TextFieldWidget(textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (keyCode == GLFW.GLFW_KEY_ENTER) { - if (this.getText().equals("^toggleuiutils")) { - SharedVariables.enabled = !SharedVariables.enabled; - if (mc.player != null) { - mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + "."), false); - } - return false; - } - - if (mc.getNetworkHandler() != null) { - if (this.getText().startsWith("/")) { - mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); - } else { - mc.getNetworkHandler().sendChatMessage(this.getText()); - } - } else { - MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); - } - - this.setText(""); - } - return super.keyPressed(keyCode, scanCode, modifiers); - } - }; - addressField.setText(""); - addressField.setMaxLength(255); - - this.addDrawableChild(addressField); + ChatTextFieldWidget chatField = new ChatTextFieldWidget(textRenderer, 6, this.height - 18, 140, 12, Text.of("Chat...")); + chatField.setText(""); + chatField.setMaxLength(255); + this.addDrawableChild(chatField); } } } diff --git a/src/main/java/com/ui_utils/mixin/BookScreenMixin.java b/src/main/java/com/ui_utils/mixin/BookScreenMixin.java index 87b9a19..61ed026 100644 --- a/src/main/java/com/ui_utils/mixin/BookScreenMixin.java +++ b/src/main/java/com/ui_utils/mixin/BookScreenMixin.java @@ -1,11 +1,10 @@ package com.ui_utils.mixin; +import com.ui_utils.gui.ChatTextFieldWidget; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.BookScreen; -import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -14,13 +13,12 @@ import com.ui_utils.MainClient; import com.ui_utils.SharedVariables; -import java.util.regex.Pattern; - @Mixin(BookScreen.class) public class BookScreenMixin extends Screen { protected BookScreenMixin(Text title) { super(title); } + @Unique private static final MinecraftClient mc = MinecraftClient.getInstance(); @@ -29,38 +27,10 @@ public void init(CallbackInfo ci) { if (SharedVariables.enabled) { MainClient.createWidgets(mc, this); - // create chat box - TextFieldWidget addressField = new TextFieldWidget(textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (keyCode == GLFW.GLFW_KEY_ENTER) { - if (this.getText().equals("^toggleuiutils")) { - SharedVariables.enabled = !SharedVariables.enabled; - if (mc.player != null) { - mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + "."), false); - } - return false; - } - - if (mc.getNetworkHandler() != null) { - if (this.getText().startsWith("/")) { - mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); - } else { - mc.getNetworkHandler().sendChatMessage(this.getText()); - } - } else { - MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); - } - - this.setText(""); - } - return super.keyPressed(keyCode, scanCode, modifiers); - } - }; - addressField.setText(""); - addressField.setMaxLength(255); - - this.addDrawableChild(addressField); + ChatTextFieldWidget chatField = new ChatTextFieldWidget(textRenderer, 6, this.height - 18, 140, 12, Text.of("Chat...")); + chatField.setText(""); + chatField.setMaxLength(255); + this.addDrawableChild(chatField); } } } diff --git a/src/main/java/com/ui_utils/mixin/ChatScreenMixin.java b/src/main/java/com/ui_utils/mixin/ChatScreenMixin.java index 2ca2223..738e7fe 100644 --- a/src/main/java/com/ui_utils/mixin/ChatScreenMixin.java +++ b/src/main/java/com/ui_utils/mixin/ChatScreenMixin.java @@ -1,5 +1,6 @@ package com.ui_utils.mixin; +import com.ui_utils.features.CommandSystem; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ChatScreen; import net.minecraft.text.Text; @@ -7,22 +8,26 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import com.ui_utils.MainClient; import com.ui_utils.SharedVariables; @Mixin(ChatScreen.class) public class ChatScreenMixin { @Inject(at = @At("HEAD"), method = "sendMessage", cancellable = true) public void sendMessage(String chatText, boolean addToHistory, CallbackInfo ci) { - if (chatText.equals("^toggleuiutils")) { - SharedVariables.enabled = !SharedVariables.enabled; - if (MinecraftClient.getInstance().player != null) { - MinecraftClient.getInstance().player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + "."), false); - } else { - MainClient.LOGGER.warn("Minecraft player was nulling while enabling / disabling UI Utils."); + MinecraftClient mc = MinecraftClient.getInstance(); + + if (chatText.startsWith(SharedVariables.commandPrefix)) { + String command = chatText.substring(SharedVariables.commandPrefix.length()); + mc.inGameHud.getChatHud().addToMessageHistory(chatText); + + String result = CommandSystem.execute(command); + if (mc.player != null && !result.isEmpty()) { + for (String line : result.split("\n")) { + mc.player.sendMessage(Text.of(line), false); + } } - MinecraftClient.getInstance().inGameHud.getChatHud().addToMessageHistory(chatText); - MinecraftClient.getInstance().setScreen(null); + + mc.setScreen(null); ci.cancel(); } } diff --git a/src/main/java/com/ui_utils/mixin/ClientConnectionMixin.java b/src/main/java/com/ui_utils/mixin/ClientConnectionMixin.java index 574f805..c30335e 100644 --- a/src/main/java/com/ui_utils/mixin/ClientConnectionMixin.java +++ b/src/main/java/com/ui_utils/mixin/ClientConnectionMixin.java @@ -1,11 +1,12 @@ package com.ui_utils.mixin; -import io.netty.channel.ChannelFutureListener; import net.minecraft.network.ClientConnection; +import net.minecraft.network.PacketCallbacks; import net.minecraft.network.packet.Packet; import net.minecraft.network.packet.c2s.play.ButtonClickC2SPacket; import net.minecraft.network.packet.c2s.play.ClickSlotC2SPacket; import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -15,22 +16,18 @@ @Mixin(ClientConnection.class) public class ClientConnectionMixin { - // called when sending any packet @Inject(at = @At("HEAD"), method = "sendImmediately", cancellable = true) - public void sendImmediately(Packet packet, ChannelFutureListener channelFutureListener, boolean flush, CallbackInfo ci) { - // checks for if packets should be sent and if the packet is a gui related packet + public void sendImmediately(Packet packet, @Nullable PacketCallbacks callbacks, boolean flush, CallbackInfo ci) { if (!SharedVariables.sendUIPackets && (packet instanceof ClickSlotC2SPacket || packet instanceof ButtonClickC2SPacket)) { ci.cancel(); return; } - // checks for if packets should be delayed and if the packet is a gui related packet and is added to a list if (SharedVariables.delayUIPackets && (packet instanceof ClickSlotC2SPacket || packet instanceof ButtonClickC2SPacket)) { SharedVariables.delayedUIPackets.add(packet); ci.cancel(); } - // cancels sign update packets if sign editing is disabled and re-enables sign editing if (!SharedVariables.shouldEditSign && (packet instanceof UpdateSignC2SPacket)) { SharedVariables.shouldEditSign = true; ci.cancel(); diff --git a/src/main/java/com/ui_utils/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/com/ui_utils/mixin/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 0000000..784bc51 --- /dev/null +++ b/src/main/java/com/ui_utils/mixin/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,17 @@ +package com.ui_utils.mixin; + +import com.ui_utils.features.PluginScanner; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.network.packet.s2c.play.CommandSuggestionsS2CPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPlayNetworkHandler.class) +public class ClientPlayNetworkHandlerMixin { + @Inject(method = "onCommandSuggestions", at = @At("HEAD")) + private void onCommandSuggestions(CommandSuggestionsS2CPacket packet, CallbackInfo ci) { + PluginScanner.onCommandSuggestions(packet); + } +} diff --git a/src/main/java/com/ui_utils/mixin/HandledScreenMixin.java b/src/main/java/com/ui_utils/mixin/HandledScreenMixin.java index 7d8a900..9bf29f2 100644 --- a/src/main/java/com/ui_utils/mixin/HandledScreenMixin.java +++ b/src/main/java/com/ui_utils/mixin/HandledScreenMixin.java @@ -1,10 +1,12 @@ package com.ui_utils.mixin; +import com.ui_utils.gui.ChatTextFieldWidget; +import com.ui_utils.gui.CustomTextFieldWidget; +import com.ui_utils.gui.UITheme; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.HandledScreen; -import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.screen.slot.Slot; import net.minecraft.screen.slot.SlotActionType; import net.minecraft.text.Text; @@ -38,61 +40,44 @@ private HandledScreenMixin() { @Unique private static final MinecraftClient mc = MinecraftClient.getInstance(); - @Unique - private TextFieldWidget addressField; + private CustomTextFieldWidget chatField; - // called when creating a HandledScreen @Inject(at = @At("TAIL"), method = "init") public void init(CallbackInfo ci) { if (SharedVariables.enabled) { MainClient.createWidgets(mc, this); - // create chat box - this.addressField = new TextFieldWidget(this.textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (keyCode == GLFW.GLFW_KEY_ENTER) { - if (this.getText().equals("^toggleuiutils")) { - SharedVariables.enabled = !SharedVariables.enabled; - if (mc.player != null) { - mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + "."), false); - - } - return false; - } - - if (mc.getNetworkHandler() != null) { - if (this.getText().startsWith("/")) { - mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); - } else { - mc.getNetworkHandler().sendChatMessage(this.getText()); - } - } else { - MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); - } - - this.setText(""); - } - return super.keyPressed(keyCode, scanCode, modifiers); - } - }; - this.addressField.setText(""); - this.addressField.setMaxLength(256); - - this.addDrawableChild(this.addressField); + this.chatField = new ChatTextFieldWidget(this.textRenderer, 6, this.height - 18, 140, 12, Text.of("Chat...")); + this.chatField.setText(""); + this.chatField.setMaxLength(256); + this.addDrawableChild(this.chatField); } } @Inject(at = @At("HEAD"), method = "keyPressed", cancellable = true) public void keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoReturnable cir) { - cir.cancel(); + if (this.chatField != null && this.chatField.isFocused()) { + if (this.chatField.keyPressed(keyCode, scanCode, modifiers)) { + cir.setReturnValue(true); + return; + } + if (keyCode == GLFW.GLFW_KEY_ESCAPE) { + this.chatField.setFocused(false); + cir.setReturnValue(true); + return; + } + cir.setReturnValue(true); + return; + } + if (super.keyPressed(keyCode, scanCode, modifiers)) { cir.setReturnValue(true); - } else if (MainClient.mc.options.inventoryKey.matchesKey(keyCode, scanCode) && (this.addressField == null || !this.addressField.isSelected())) { - // Crashes if address field does not exist (because of ui utils disabled, this is a temporary fix.) + cir.cancel(); + } else if (MainClient.mc.options.inventoryKey.matchesKey(keyCode, scanCode)) { this.close(); cir.setReturnValue(true); + cir.cancel(); } else { this.handleHotbarKeyPressed(keyCode, scanCode); if (this.focusedSlot != null && this.focusedSlot.hasStack()) { @@ -102,17 +87,19 @@ public void keyPressed(int keyCode, int scanCode, int modifiers, CallbackInfoRet this.onMouseClick(this.focusedSlot, this.focusedSlot.id, hasControlDown() ? 1 : 0, SlotActionType.THROW); } } + } + } - cir.setReturnValue(true); + @Override + public boolean charTyped(char chr, int modifiers) { + if (this.chatField != null && this.chatField.isFocused()) { + return this.chatField.charTyped(chr, modifiers); } + return super.charTyped(chr, modifiers); } - // inject at the end of the render method @Inject(at = @At("TAIL"), method = "render") public void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { - // display sync id, revision, if ui utils is enabled - // this hurts me physically to look at this in a render method :( - // im too lazy to fix it tho :D if (SharedVariables.enabled) { MainClient.createText(mc, context, this.textRenderer); } diff --git a/src/main/java/com/ui_utils/mixin/MultiplayerScreenMixin.java b/src/main/java/com/ui_utils/mixin/MultiplayerScreenMixin.java index b327c88..c455be7 100644 --- a/src/main/java/com/ui_utils/mixin/MultiplayerScreenMixin.java +++ b/src/main/java/com/ui_utils/mixin/MultiplayerScreenMixin.java @@ -1,14 +1,23 @@ package com.ui_utils.mixin; +import com.ui_utils.gui.CustomButtonWidget; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; -import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.session.Session; import net.minecraft.text.Text; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import com.ui_utils.SharedVariables; +import net.fabricmc.loader.api.FabricLoader; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; @Mixin(MultiplayerScreen.class) public class MultiplayerScreenMixin extends Screen { @@ -16,20 +25,164 @@ private MultiplayerScreenMixin() { super(null); } + @Inject(at = @At("RETURN"), method = "init", require = 0) + private void replaceViaFabricPlusButton(CallbackInfo ci) { + if (!SharedVariables.enabled) return; + if (!FabricLoader.getInstance().isModLoaded("viafabricplus")) return; + + net.minecraft.client.gui.widget.ButtonWidget vfpButton = null; + for (var child : this.children()) { + if (child instanceof net.minecraft.client.gui.widget.ButtonWidget button) { + String msg = button.getMessage().getString(); + if (msg.contains("ViaFabricPlus") || msg.equals("ViaFabricPlus")) { + vfpButton = button; + break; + } + } + } + + if (vfpButton != null) { + final net.minecraft.client.gui.widget.ButtonWidget originalButton = vfpButton; + + this.remove(vfpButton); + + int margin = 5; + int spacing = 4; + int buttonWidth = 50; + int buttonHeight = 14; + int rightX = this.width - margin - buttonWidth; + int bottomY = this.height - 60; + + this.addDrawableChild(CustomButtonWidget.createSmall(rightX, bottomY + buttonHeight + spacing, buttonWidth, Text.of("Via+"), (button) -> { + originalButton.onPress(); + })); + } + } + @Inject(at = @At("TAIL"), method = "init") public void init(CallbackInfo ci) { if (SharedVariables.enabled) { - // Create "Bypass Resource Pack" option - this.addDrawableChild(ButtonWidget.builder(Text.of("Bypass Resource Pack: " + (SharedVariables.bypassResourcePack ? "ON" : "OFF")), (button) -> { + MinecraftClient mc = MinecraftClient.getInstance(); + int margin = 5; + int spacing = 4; + int buttonWidth = 50; + int buttonHeight = 14; + int bottomY = this.height - 60; + + this.addDrawableChild(CustomButtonWidget.createSmall(margin, bottomY, buttonWidth, Text.of("Bypass"), (button) -> { SharedVariables.bypassResourcePack = !SharedVariables.bypassResourcePack; - button.setMessage(Text.of("Bypass Resource Pack: " + (SharedVariables.bypassResourcePack ? "ON" : "OFF"))); - }).width(160).position(this.width - 170, this.height - 50).build()); + button.setMessage(Text.of(SharedVariables.bypassResourcePack ? "§aBypass" : "Bypass")); + })); - // Create "Force Deny" option - this.addDrawableChild(ButtonWidget.builder(Text.of("Force Deny: " + (SharedVariables.resourcePackForceDeny ? "ON" : "OFF")), (button) -> { + this.addDrawableChild(CustomButtonWidget.createSmall(margin, bottomY + buttonHeight + spacing, buttonWidth, Text.of("Deny"), (button) -> { SharedVariables.resourcePackForceDeny = !SharedVariables.resourcePackForceDeny; - button.setMessage(Text.of("Force Deny: " + (SharedVariables.resourcePackForceDeny ? "ON" : "OFF"))); - }).width(160).position(this.width - 170, this.height - 25).build()); + button.setMessage(Text.of(SharedVariables.resourcePackForceDeny ? "§aDeny" : "Deny")); + })); + + int rightX = this.width - margin - buttonWidth; + + this.addDrawableChild(CustomButtonWidget.createSmall(rightX, bottomY, buttonWidth, Text.of("User"), (button) -> { + mc.setScreen(new UsernameScreen((MultiplayerScreen)(Object)this, mc)); + })); + + if (!FabricLoader.getInstance().isModLoaded("viafabricplus")) { + this.addDrawableChild(CustomButtonWidget.createSmall(rightX, bottomY + buttonHeight + spacing, buttonWidth, Text.of("Via+"), (button) -> { + boolean isViaFabric = FabricLoader.getInstance().isModLoaded("viafabric"); + + if (isViaFabric) { + try { + Class viaScreenClass = Class.forName("com.viaversion.fabric.mc121.gui.ViaConfigScreen"); + Screen screen = (Screen) viaScreenClass.getConstructor(Screen.class).newInstance((MultiplayerScreen)(Object)this); + mc.setScreen(screen); + } catch (Exception e1) { + try { + Class viaScreenClass = Class.forName("com.github.creeper123123321.viafabric.gui.ViaConfigScreen"); + Screen screen = (Screen) viaScreenClass.getConstructor(Screen.class).newInstance((MultiplayerScreen)(Object)this); + mc.setScreen(screen); + } catch (Exception e2) { + mc.setScreen(new ViaNotFoundScreen((MultiplayerScreen)(Object)this, "Error opening ViaFabric")); + } + } + } else { + mc.setScreen(new ViaNotFoundScreen((MultiplayerScreen)(Object)this, "ViaFabric(Plus) not installed!")); + } + })); + } + } + } + + private static class UsernameScreen extends Screen { + private final Screen parent; + private final MinecraftClient mc; + private net.minecraft.client.gui.widget.TextFieldWidget usernameField; + + protected UsernameScreen(Screen parent, MinecraftClient mc) { + super(Text.of("Set Username")); + this.parent = parent; + this.mc = mc; + } + + @Override + protected void init() { + int centerX = this.width / 2; + int centerY = this.height / 2; + + this.usernameField = new net.minecraft.client.gui.widget.TextFieldWidget(this.textRenderer, centerX - 100, centerY - 20, 200, 20, Text.of("Username")); + this.usernameField.setText(mc.getSession().getUsername()); + this.usernameField.setMaxLength(16); + this.addDrawableChild(this.usernameField); + + this.addDrawableChild(net.minecraft.client.gui.widget.ButtonWidget.builder(Text.of("Apply"), (button) -> { + String newName = this.usernameField.getText().trim(); + if (!newName.isEmpty()) { + try { + Session oldSession = mc.getSession(); + Session newSession = new Session(newName, oldSession.getUuidOrNull(), oldSession.getAccessToken(), Optional.empty(), Optional.empty(), Session.AccountType.MOJANG); + java.lang.reflect.Field sessionField = MinecraftClient.class.getDeclaredField("session"); + sessionField.setAccessible(true); + sessionField.set(mc, newSession); + } catch (Exception ignored) {} + } + mc.setScreen(parent); + }).dimensions(centerX - 100, centerY + 10, 95, 20).build()); + + this.addDrawableChild(net.minecraft.client.gui.widget.ButtonWidget.builder(Text.of("Cancel"), (button) -> { + mc.setScreen(parent); + }).dimensions(centerX + 5, centerY + 10, 95, 20).build()); + } + + @Override + public void render(net.minecraft.client.gui.DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, this.height / 2 - 40, 0xFFFFFF); + } + } + + private static class ViaNotFoundScreen extends Screen { + private final Screen parent; + private final String message; + + protected ViaNotFoundScreen(Screen parent, String message) { + super(Text.of("ViaFabricPlus")); + this.parent = parent; + this.message = message; + } + + @Override + protected void init() { + int centerX = this.width / 2; + int centerY = this.height / 2; + + this.addDrawableChild(net.minecraft.client.gui.widget.ButtonWidget.builder(Text.of("OK"), (button) -> { + MinecraftClient.getInstance().setScreen(parent); + }).dimensions(centerX - 50, centerY + 20, 100, 20).build()); + } + + @Override + public void render(net.minecraft.client.gui.DrawContext context, int mouseX, int mouseY, float delta) { + super.render(context, mouseX, mouseY, delta); + context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, this.height / 2 - 30, 0xFFFFFF); + context.drawCenteredTextWithShadow(this.textRenderer, Text.of(message), this.width / 2, this.height / 2, 0xFF5555); } } } diff --git a/src/main/java/com/ui_utils/mixin/ScreenMixin.java b/src/main/java/com/ui_utils/mixin/ScreenMixin.java index 69439f2..c7ea527 100644 --- a/src/main/java/com/ui_utils/mixin/ScreenMixin.java +++ b/src/main/java/com/ui_utils/mixin/ScreenMixin.java @@ -1,5 +1,7 @@ package com.ui_utils.mixin; +import com.ui_utils.gui.ChatTextFieldWidget; +import com.ui_utils.gui.CustomTextFieldWidget; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -8,9 +10,7 @@ import net.minecraft.client.gui.Selectable; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.LecternScreen; -import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.text.Text; -import org.lwjgl.glfw.GLFW; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -20,8 +20,6 @@ import com.ui_utils.SharedVariables; import com.ui_utils.mixin.accessor.ScreenAccessor; -import java.util.regex.Pattern; - @SuppressWarnings("all") @Mixin(Screen.class) public abstract class ScreenMixin { @@ -29,65 +27,25 @@ public abstract class ScreenMixin { public abstract T addDrawableChild(T drawableElement); private static final MinecraftClient mc = MinecraftClient.getInstance(); - - private TextFieldWidget addressField; + private CustomTextFieldWidget chatField; private boolean initialized = false; - // inject at the end of the render method (if instanceof LecternScreen) @Inject(at = @At("TAIL"), method = "init(Lnet/minecraft/client/MinecraftClient;II)V") public void init(MinecraftClient client, int width, int height, CallbackInfo ci) { - // check if the current gui is a lectern gui and if ui-utils is enabled if (mc.currentScreen instanceof LecternScreen screen && SharedVariables.enabled) { - // setup widgets - if (/*!this.initialized*/ true) { // bro why did you do this cxg :skull: - // check if the current gui is a lectern gui and ui-utils is enabled - // if you do not message me about this @coderx-gamer you are not reading my commits - // why would you read them anyway tbh - // ill clean this up later if you dont fix it - - TextRenderer textRenderer = ((ScreenAccessor) this).getTextRenderer(); - MainClient.createWidgets(mc, screen); - - // create chat box - this.addressField = new TextFieldWidget(textRenderer, 5, 245, 160, 20, Text.of("Chat ...")) { - @Override - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (keyCode == GLFW.GLFW_KEY_ENTER) { - if (this.getText().equals("^toggleuiutils")) { - SharedVariables.enabled = !SharedVariables.enabled; - if (mc.player != null) { - mc.player.sendMessage(Text.of("UI-Utils is now " + (SharedVariables.enabled ? "enabled" : "disabled") + "."), false); - } - return false; - } - - if (mc.getNetworkHandler() != null) { - if (this.getText().startsWith("/")) { - mc.getNetworkHandler().sendChatCommand(this.getText().replaceFirst(Pattern.quote("/"), "")); - } else { - mc.getNetworkHandler().sendChatMessage(this.getText()); - } - } else { - MainClient.LOGGER.warn("Minecraft network handler (mc.getNetworkHandler()) was null while trying to send chat message from UI Utils."); - } - - this.setText(""); - } - return super.keyPressed(keyCode, scanCode, modifiers); - } - }; - this.addressField.setText(""); - this.addressField.setMaxLength(255); - - this.addDrawableChild(this.addressField); - this.initialized = true; - } + TextRenderer textRenderer = ((ScreenAccessor) this).getTextRenderer(); + MainClient.createWidgets(mc, screen); + + this.chatField = new ChatTextFieldWidget(textRenderer, 6, height - 18, 140, 12, Text.of("Chat...")); + this.chatField.setText(""); + this.chatField.setMaxLength(255); + this.addDrawableChild(this.chatField); + this.initialized = true; } } @Inject(at = @At("TAIL"), method = "render") public void render(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) { - // display sync id, revision, if ui utils is enabled if (SharedVariables.enabled && mc.player != null && mc.currentScreen instanceof LecternScreen) { MainClient.createText(mc, context, ((ScreenAccessor) this).getTextRenderer()); } diff --git a/src/main/java/com/ui_utils/mixin/SignEditScreenMixin.java b/src/main/java/com/ui_utils/mixin/SignEditScreenMixin.java index 01ada52..e7dc556 100644 --- a/src/main/java/com/ui_utils/mixin/SignEditScreenMixin.java +++ b/src/main/java/com/ui_utils/mixin/SignEditScreenMixin.java @@ -1,9 +1,9 @@ package com.ui_utils.mixin; +import com.ui_utils.gui.CustomButtonWidget; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.SignEditScreen; -import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.text.Text; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -21,22 +21,18 @@ protected SignEditScreenMixin(Text title) { @Unique private static final MinecraftClient mc = MinecraftClient.getInstance(); - // called when any sign edit screen is created @Inject(at = @At("TAIL"), method = "init") public void init(CallbackInfo ci) { - - // register "close without packet" button for SignEditScreen if ui utils is enabled if (SharedVariables.enabled) { - addDrawableChild(ButtonWidget.builder(Text.of("Close without packet"), (button) -> { - // disables sign editing and closes the current gui without sending a packet + addDrawableChild(CustomButtonWidget.create(4, 4, 95, Text.of("Close silent"), (button) -> { SharedVariables.shouldEditSign = false; mc.setScreen(null); - }).width(115).position(5, 5).build()); - addDrawableChild(ButtonWidget.builder(Text.of("Disconnect"), (button) -> { + })); + addDrawableChild(CustomButtonWidget.create(4, 24, 95, Text.of("Disconnect"), (button) -> { if (mc.getNetworkHandler() != null) { - mc.getNetworkHandler().getConnection().disconnect(Text.of("Disconnecting (UI-UTILS)")); + mc.getNetworkHandler().getConnection().disconnect(Text.of("Disconnecting")); } - }).width(115).position(5, 35).build()); + })); } } } diff --git a/src/main/java/com/ui_utils/mixin/SleepingChatScreenMixin.java b/src/main/java/com/ui_utils/mixin/SleepingChatScreenMixin.java index b04504f..91fb78d 100644 --- a/src/main/java/com/ui_utils/mixin/SleepingChatScreenMixin.java +++ b/src/main/java/com/ui_utils/mixin/SleepingChatScreenMixin.java @@ -1,8 +1,8 @@ package com.ui_utils.mixin; +import com.ui_utils.gui.CustomButtonWidget; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.SleepingChatScreen; -import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.text.Text; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -16,18 +16,15 @@ protected SleepingChatScreenMixin(Text title) { super(title); } - // called when SleepingChatScreen is created @Inject(at = @At("TAIL"), method = "init") public void init(CallbackInfo ci) { - // register "client wake up" button for SleepingChatScreen if ui utils is enabled if (SharedVariables.enabled) { - addDrawableChild(ButtonWidget.builder(Text.of("Client wake up"), (button) -> { - // wakes the player up client-side + addDrawableChild(CustomButtonWidget.create(4, 4, 95, Text.of("Client wake"), (button) -> { if (this.client != null && this.client.player != null) { this.client.player.wakeUp(); this.client.setScreen(null); } - }).width(115).position(5, 5).build()); + })); } } } diff --git a/src/main/java/com/ui_utils/mixin/TitleScreenMixin.java b/src/main/java/com/ui_utils/mixin/TitleScreenMixin.java index 59a3234..70e8f20 100644 --- a/src/main/java/com/ui_utils/mixin/TitleScreenMixin.java +++ b/src/main/java/com/ui_utils/mixin/TitleScreenMixin.java @@ -1,16 +1,14 @@ package com.ui_utils.mixin; +import com.ui_utils.gui.CustomButtonWidget; +import com.ui_utils.gui.UITheme; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.ButtonTextures; +import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.TitleScreen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.TextWidget; -import net.minecraft.client.gui.widget.TexturedButtonWidget; import net.minecraft.client.toast.SystemToast; import net.minecraft.client.toast.ToastManager; import net.minecraft.text.Text; -import net.minecraft.util.Identifier; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -29,26 +27,13 @@ private void onInitWidgetsNormal(CallbackInfo ci) { if (!UpdateUtils.messageShown) { MinecraftClient client = MinecraftClient.getInstance(); ToastManager toastManager = client.getToastManager(); - Text title = Text.of("UI-Utils " + UpdateUtils.version + " is out for " + UpdateUtils.mcVersion + "!"); - Text description = Text.of("Download it from the top left corner!"); + Text title = Text.of("UI-Utils " + UpdateUtils.version + " available!"); + Text description = Text.of("Click update button to download"); SystemToast.add(toastManager, new SystemToast.Type(30000L), title, description); UpdateUtils.messageShown = true; } - Text message = Text.of("Download UI-Utils " + UpdateUtils.version + "!"); - - this.addDrawableChild(new TextWidget(40 - 15, 5, textRenderer.getWidth(message), textRenderer.fontHeight, message, textRenderer)); - - ButtonWidget downloadUpdateButton = new TexturedButtonWidget(5, 5 - 3, - 15, 15, - new ButtonTextures( - Identifier.of("ui_utils", "update"), - Identifier.of("ui_utils", "update_selected") - ), - (button) -> UpdateUtils.downloadUpdate(), - Text.of("Download Update")); - this.addDrawableChild(downloadUpdateButton); - + this.addDrawableChild(CustomButtonWidget.createSmall(4, 4, 80, Text.of("Update v" + UpdateUtils.version), (button) -> UpdateUtils.downloadUpdate())); } } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 7322ab9..5e54256 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -31,10 +31,10 @@ ], "depends": { - "fabricloader": ">=0.16.9", - "fabric": "*", - "minecraft": ["~1.21.6"], - "java": ">=17" + "fabricloader": ">=0.15.0", + "fabric-api": "*", + "minecraft": ">=1.21 <=1.21.99", + "java": ">=21" }, "accessWidener": "ui_utils.accesswidener", diff --git a/src/main/resources/ui_utils.mixins.json b/src/main/resources/ui_utils.mixins.json index ff81ea8..c1c3a46 100644 --- a/src/main/resources/ui_utils.mixins.json +++ b/src/main/resources/ui_utils.mixins.json @@ -3,12 +3,14 @@ "minVersion": "0.8", "package": "com.ui_utils.mixin", "compatibilityLevel": "JAVA_17", + "priority": 2000, "client": [ "BookEditScreenMixin", "BookScreenMixin", "ChatScreenMixin", "ClientCommonNetworkHandlerMixin", "ClientConnectionMixin", + "ClientPlayNetworkHandlerMixin", "HandledScreenMixin", "MultiplayerScreenMixin", "ScreenMixin",