From cce703122b05d92e3278fb797759446cdcf3b053 Mon Sep 17 00:00:00 2001 From: retuci0 Date: Thu, 21 Aug 2025 16:14:14 +0200 Subject: [PATCH] v1.0 --- .gitattributes | 9 + .github/workflows/build.yml | 30 +++ .gitignore | 42 +++ LICENSE | 121 +++++++++ build.gradle | 78 ++++++ credits.txt | 3 + gradle.properties | 18 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 251 ++++++++++++++++++ gradlew.bat | 94 +++++++ settings.gradle | 10 + .../me/retucio/camtweaks/CameraTweaks.java | 114 ++++++++ .../me/retucio/camtweaks/command/Command.java | 77 ++++++ .../camtweaks/command/CommandManager.java | 66 +++++ .../command/args/ModuleArgumentType.java | 49 ++++ .../command/commands/BindCommand.java | 55 ++++ .../command/commands/PrefixCommand.java | 28 ++ .../command/commands/SendCommand.java | 26 ++ .../command/commands/ToggleCommand.java | 58 ++++ .../me/retucio/camtweaks/event/Event.java | 14 + .../me/retucio/camtweaks/event/EventBus.java | 55 ++++ .../me/retucio/camtweaks/event/Subscribe.java | 8 + .../event/events/ClientClickEvent.java | 22 ++ .../camtweaks/event/events/GetFOVEvent.java | 20 ++ .../event/events/HeldItemRendererEvent.java | 32 +++ .../camtweaks/event/events/KeyEvent.java | 27 ++ .../event/events/MouseClickEvent.java | 22 ++ .../event/events/MouseScrollEvent.java | 30 +++ .../event/events/PerspectiveChangeEvent.java | 22 ++ .../event/events/ReceiveMessageEvent.java | 43 +++ .../event/events/RenderArmEvent.java | 32 +++ .../camtweaks/event/events/RenderEvent.java | 6 + .../event/events/SendMessageEvent.java | 20 ++ .../retucio/camtweaks/mixin/CameraMixin.java | 29 ++ .../camtweaks/mixin/ChatHudLineMixin.java | 38 +++ .../mixin/ChatHudLineVisibleMixin.java | 55 ++++ .../retucio/camtweaks/mixin/ChatHudMixin.java | 194 ++++++++++++++ .../mixin/ChatInputSuggestorMixin.java | 63 +++++ .../camtweaks/mixin/ChatScreenMixin.java | 25 ++ .../mixin/ClientPlayNetworkHandlerMixin.java | 49 ++++ .../camtweaks/mixin/GameOptionsMixin.java | 22 ++ .../camtweaks/mixin/GameRendererMixin.java | 40 +++ .../mixin/HeldItemRendererMixin.java | 49 ++++ .../camtweaks/mixin/InGameHudMixin.java | 28 ++ .../camtweaks/mixin/KeyboardMixin.java | 23 ++ .../mixin/LightmapTextureManagerMixin.java | 37 +++ .../camtweaks/mixin/MessageHandlerMixin.java | 37 +++ .../camtweaks/mixin/MinecraftClientMixin.java | 17 ++ .../retucio/camtweaks/mixin/MouseMixin.java | 28 ++ .../camtweaks/mixin/StringHelperMixin.java | 18 ++ .../StatusEffectInstanceAccessor.java | 12 + .../me/retucio/camtweaks/module/Module.java | 139 ++++++++++ .../camtweaks/module/ModuleManager.java | 67 +++++ .../camtweaks/module/modules/BetterChat.java | 214 +++++++++++++++ .../camtweaks/module/modules/Fullbright.java | 58 ++++ .../retucio/camtweaks/module/modules/GUI.java | 49 ++++ .../camtweaks/module/modules/HandView.java | 140 ++++++++++ .../camtweaks/module/modules/Perspective.java | 61 +++++ .../camtweaks/module/modules/Zoom.java | 99 +++++++ .../module/settings/BooleanSetting.java | 50 ++++ .../module/settings/EnumSetting.java | 87 ++++++ .../camtweaks/module/settings/KeySetting.java | 49 ++++ .../module/settings/NumberSetting.java | 82 ++++++ .../camtweaks/module/settings/Setting.java | 30 +++ .../module/settings/StringSetting.java | 50 ++++ .../me/retucio/camtweaks/ui/ClickGUI.java | 111 ++++++++ .../java/me/retucio/camtweaks/ui/HUD.java | 13 + .../me/retucio/camtweaks/ui/ModuleButton.java | 68 +++++ .../me/retucio/camtweaks/ui/ModuleFrame.java | 116 ++++++++ .../camtweaks/ui/settings/BindButton.java | 74 ++++++ .../ui/settings/ClickGUISettingsFrame.java | 53 ++++ .../camtweaks/ui/settings/CycleButton.java | 84 ++++++ .../camtweaks/ui/settings/SettingButton.java | 28 ++ .../camtweaks/ui/settings/SettingsFrame.java | 152 +++++++++++ .../camtweaks/ui/settings/SliderButton.java | 76 ++++++ .../camtweaks/ui/settings/TextButton.java | 97 +++++++ .../camtweaks/ui/settings/ToggleButton.java | 63 +++++ .../me/retucio/camtweaks/util/ChatUtil.java | 60 +++++ .../me/retucio/camtweaks/util/Colors.java | 41 +++ .../me/retucio/camtweaks/util/KeyUtil.java | 45 ++++ .../camtweaks/util/interfaces/IChatHud.java | 8 + .../util/interfaces/IChatHudLine.java | 14 + .../util/interfaces/IChatHudLineVisible.java | 7 + .../util/interfaces/IMessageHandler.java | 8 + .../util/interfaces/TextVisitor.java | 41 +++ src/main/resources/assets/camtweaks/icon.png | Bin 0 -> 4724 bytes .../resources/assets/camtweaks/icon_chat.png | Bin 0 -> 3424 bytes src/main/resources/camtweaks.mixins.json | 33 +++ src/main/resources/fabric.mod.json | 34 +++ 90 files changed, 4654 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/build.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 build.gradle create mode 100644 credits.txt create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/me/retucio/camtweaks/CameraTweaks.java create mode 100644 src/main/java/me/retucio/camtweaks/command/Command.java create mode 100644 src/main/java/me/retucio/camtweaks/command/CommandManager.java create mode 100644 src/main/java/me/retucio/camtweaks/command/args/ModuleArgumentType.java create mode 100644 src/main/java/me/retucio/camtweaks/command/commands/BindCommand.java create mode 100644 src/main/java/me/retucio/camtweaks/command/commands/PrefixCommand.java create mode 100644 src/main/java/me/retucio/camtweaks/command/commands/SendCommand.java create mode 100644 src/main/java/me/retucio/camtweaks/command/commands/ToggleCommand.java create mode 100644 src/main/java/me/retucio/camtweaks/event/Event.java create mode 100644 src/main/java/me/retucio/camtweaks/event/EventBus.java create mode 100644 src/main/java/me/retucio/camtweaks/event/Subscribe.java create mode 100644 src/main/java/me/retucio/camtweaks/event/events/ClientClickEvent.java create mode 100644 src/main/java/me/retucio/camtweaks/event/events/GetFOVEvent.java create mode 100644 src/main/java/me/retucio/camtweaks/event/events/HeldItemRendererEvent.java create mode 100644 src/main/java/me/retucio/camtweaks/event/events/KeyEvent.java create mode 100644 src/main/java/me/retucio/camtweaks/event/events/MouseClickEvent.java create mode 100644 src/main/java/me/retucio/camtweaks/event/events/MouseScrollEvent.java create mode 100644 src/main/java/me/retucio/camtweaks/event/events/PerspectiveChangeEvent.java create mode 100644 src/main/java/me/retucio/camtweaks/event/events/ReceiveMessageEvent.java create mode 100644 src/main/java/me/retucio/camtweaks/event/events/RenderArmEvent.java create mode 100644 src/main/java/me/retucio/camtweaks/event/events/RenderEvent.java create mode 100644 src/main/java/me/retucio/camtweaks/event/events/SendMessageEvent.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/CameraMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/ChatHudLineMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/ChatHudLineVisibleMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/ChatHudMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/ChatInputSuggestorMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/ChatScreenMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/ClientPlayNetworkHandlerMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/GameOptionsMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/GameRendererMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/HeldItemRendererMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/InGameHudMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/KeyboardMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/LightmapTextureManagerMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/MessageHandlerMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/MinecraftClientMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/MouseMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/StringHelperMixin.java create mode 100644 src/main/java/me/retucio/camtweaks/mixin/accessor/StatusEffectInstanceAccessor.java create mode 100644 src/main/java/me/retucio/camtweaks/module/Module.java create mode 100644 src/main/java/me/retucio/camtweaks/module/ModuleManager.java create mode 100644 src/main/java/me/retucio/camtweaks/module/modules/BetterChat.java create mode 100644 src/main/java/me/retucio/camtweaks/module/modules/Fullbright.java create mode 100644 src/main/java/me/retucio/camtweaks/module/modules/GUI.java create mode 100644 src/main/java/me/retucio/camtweaks/module/modules/HandView.java create mode 100644 src/main/java/me/retucio/camtweaks/module/modules/Perspective.java create mode 100644 src/main/java/me/retucio/camtweaks/module/modules/Zoom.java create mode 100644 src/main/java/me/retucio/camtweaks/module/settings/BooleanSetting.java create mode 100644 src/main/java/me/retucio/camtweaks/module/settings/EnumSetting.java create mode 100644 src/main/java/me/retucio/camtweaks/module/settings/KeySetting.java create mode 100644 src/main/java/me/retucio/camtweaks/module/settings/NumberSetting.java create mode 100644 src/main/java/me/retucio/camtweaks/module/settings/Setting.java create mode 100644 src/main/java/me/retucio/camtweaks/module/settings/StringSetting.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/ClickGUI.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/HUD.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/ModuleButton.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/ModuleFrame.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/settings/BindButton.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/settings/ClickGUISettingsFrame.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/settings/CycleButton.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/settings/SettingButton.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/settings/SettingsFrame.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/settings/SliderButton.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/settings/TextButton.java create mode 100644 src/main/java/me/retucio/camtweaks/ui/settings/ToggleButton.java create mode 100644 src/main/java/me/retucio/camtweaks/util/ChatUtil.java create mode 100644 src/main/java/me/retucio/camtweaks/util/Colors.java create mode 100644 src/main/java/me/retucio/camtweaks/util/KeyUtil.java create mode 100644 src/main/java/me/retucio/camtweaks/util/interfaces/IChatHud.java create mode 100644 src/main/java/me/retucio/camtweaks/util/interfaces/IChatHudLine.java create mode 100644 src/main/java/me/retucio/camtweaks/util/interfaces/IChatHudLineVisible.java create mode 100644 src/main/java/me/retucio/camtweaks/util/interfaces/IMessageHandler.java create mode 100644 src/main/java/me/retucio/camtweaks/util/interfaces/TextVisitor.java create mode 100644 src/main/resources/assets/camtweaks/icon.png create mode 100644 src/main/resources/assets/camtweaks/icon_chat.png create mode 100644 src/main/resources/camtweaks.mixins.json create mode 100644 src/main/resources/fabric.mod.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8fc3827 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,30 @@ +# Automatically build the project and run any configured tests for every push +# and submitted pull request. This can help catch issues that only occur on +# certain platforms or Java versions, and provides a first line of defence +# against bad commits. + +name: build +on: [pull_request, push] + +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - name: checkout repository + uses: actions/checkout@v4 + - name: validate gradle wrapper + uses: gradle/actions/wrapper-validation@v4 + - name: setup jdk + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'microsoft' + - name: make gradle wrapper executable + run: chmod +x ./gradlew + - name: build + run: ./gradlew build + - name: capture build artifacts + uses: actions/upload-artifact@v4 + with: + name: Artifacts + path: build/libs/ \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a601a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# gradle + +.gradle/ +build/ +out/ +classes/ + +# eclipse + +*.launch + +# idea + +.idea/ +*.iml +*.ipr +*.iws + +# vscode + +.settings/ +.vscode/ +bin/ +.classpath +.project + +# macos + +*.DS_Store + +# fabric + +run/ + +# java + +hs_err_*.log +replay_*.log +*.hprof +*.jfr + +todo.txt \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1625c17 --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..79a6902 --- /dev/null +++ b/build.gradle @@ -0,0 +1,78 @@ +plugins { + id 'fabric-loom' version "${loom_version}" + id 'maven-publish' +} + +version = project.mod_version +group = project.maven_group + +base { + archivesName = project.archives_base_name +} + +repositories { + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about 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}" + +} + +processResources { + inputs.property "version", project.version + + filesMatching("fabric.mod.json") { + expand "version": inputs.properties.version + } +} + +tasks.withType(JavaCompile).configureEach { + it.options.release = 21 +} + +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() + + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +jar { + inputs.property "archivesName", project.base.archivesName + + from("LICENSE") { + rename { "${it}_${inputs.properties.archivesName}"} + } +} + +// configure the maven publication +publishing { + publications { + create("mavenJava", MavenPublication) { + artifactId = project.archives_base_name + from components.java + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} \ No newline at end of file diff --git a/credits.txt b/credits.txt new file mode 100644 index 0000000..11b5c0e --- /dev/null +++ b/credits.txt @@ -0,0 +1,3 @@ +meteor client +oyvey client (1.21 port by mioclient) +lennyboi (youtube) \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..b2bef23 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +org.gradle.parallel=true + +# Fabric Properties +# check these on https://fabricmc.net/develop +minecraft_version=1.21.6 +yarn_mappings=1.21.6+build.1 +loader_version=0.17.2 +loom_version=1.11-SNAPSHOT + +# Mod Properties +mod_version=1.0.0 +maven_group=me.retucio +archives_base_name=camtweaks + +# Dependencies +fabric_version=0.128.2+1.21.6 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b33c55baabb587c669f562ae36f953de2481846 GIT binary patch literal 43764 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..75c4d72 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + mavenCentral() + gradlePluginPortal() + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/CameraTweaks.java b/src/main/java/me/retucio/camtweaks/CameraTweaks.java new file mode 100644 index 0000000..dfed583 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/CameraTweaks.java @@ -0,0 +1,114 @@ +package me.retucio.camtweaks; + +import me.retucio.camtweaks.command.CommandManager; +import me.retucio.camtweaks.command.commands.BindCommand; +import me.retucio.camtweaks.event.EventBus; +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.module.ModuleManager; +import me.retucio.camtweaks.ui.ClickGUI; +import me.retucio.camtweaks.ui.settings.*; +import net.fabricmc.api.ModInitializer; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.lwjgl.glfw.GLFW; + +public class CameraTweaks implements ModInitializer { + + // cosas necesarias + public static final String MOD_ID = "camtweaks"; + public static final CameraTweaks INSTANCE = new CameraTweaks(); + public static final Logger LOGGER = LogManager.getLogger(CameraTweaks.class); + public static final EventBus EVENT_BUS = new EventBus(); + public static final MinecraftClient mc = MinecraftClient.getInstance(); + + private Screen prevScreen; + + // managers + public static ModuleManager moduleManager = new ModuleManager(EVENT_BUS); + public static CommandManager commandManager = new CommandManager(); + + @Override + public void onInitialize() {} + + // se ejecuta cada vez que se presiona una tecla + public void onKeyPress(int key, int action) { + moduleManager.getEnabledModules().forEach(module -> module.onKey(key, action)); + + boolean anyFocused = isAnySettingButtonFocused(); + + if (action == GLFW.GLFW_PRESS) { + handleModuleToggle(key, anyFocused); + handleSettingButtonsKey(key, action); + handleClickGUIKey(key, anyFocused); + BindCommand.onKeyPress(key); + } else if (action == GLFW.GLFW_RELEASE && !anyFocused) { + handleModuleRelease(key); + } + } + + // verifica si algún botón de ajustes está escuchando + private boolean isAnySettingButtonFocused() { + for (SettingsFrame sf : ClickGUI.INSTANCE.getSettingsFrames()) { + for (SettingButton sb : sf.getButtons()) { + if ((sb instanceof BindButton b && b.isFocused()) || (sb instanceof TextButton t && t.isFocused())) + return true; + } + } + return false; + } + + // se ocupa de la lógica de encendido y apagado de los módulos + private void handleModuleToggle(int key, boolean anyFocused) { + if (mc.currentScreen != null && mc.currentScreen != ClickGUI.INSTANCE) return; + + for (Module module : moduleManager.getModules()) { + if (key != module.getKey() || anyFocused) continue; + + if (module.shouldToggleOnBindRelease() && !module.isEnabled()) + module.setEnabled(true); + else if (!module.shouldToggleOnBindRelease()) + module.toggle(); + } + } + + // se ocupa de hacer los botones de ajustes que lo necesiten escuchar teclas + private void handleSettingButtonsKey(int key, int action) { + for (SettingsFrame sf : ClickGUI.INSTANCE.getSettingsFrames()) { + for (SettingButton sb : sf.getButtons()) { + if (sb instanceof BindButton b) b.onKey(key, action); + else if (sb instanceof TextButton t) t.onKey(key, action); + } + } + } + + // maneja la lógica de apertura de la interfaz + private void handleClickGUIKey(int key, boolean anyFocused) { + if (key != ClickGUISettingsFrame.guiSettings.getKey() || anyFocused) return; + + if (mc.currentScreen != ClickGUI.INSTANCE) { + prevScreen = mc.currentScreen; + mc.setScreen(ClickGUI.INSTANCE); + } else { + mc.setScreen(prevScreen); + } + } + + // se ocupa de apagar los módulos que tengan configurado hacerlo tras soltar su tecla + private void handleModuleRelease(int key) { + if (mc.currentScreen != null && mc.currentScreen != ClickGUI.INSTANCE) return; + + for (Module module : moduleManager.getEnabledModules()) { + if (module.shouldToggleOnBindRelease() && key == module.getKey()) { + module.setEnabled(false); + } + } + } + + // se ejecuta cada tick, es decir, 20 veces por segundo + public void onTick() { + if (mc.player == null) return; + moduleManager.getEnabledModules().forEach(Module::onTick); + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/command/Command.java b/src/main/java/me/retucio/camtweaks/command/Command.java new file mode 100644 index 0000000..8c8d1d4 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/command/Command.java @@ -0,0 +1,77 @@ +package me.retucio.camtweaks.command; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.command.CommandSource; +import net.minecraft.registry.BuiltinRegistries; +import net.minecraft.server.command.CommandManager; + +import java.util.List; + +import static me.retucio.camtweaks.CameraTweaks.commandManager; + +public abstract class Command { + + // utilizar los "registries" ya existentes de Brigadier para el autocompletado + protected static final CommandRegistryAccess REGISTRY_ACCESS = CommandManager.createRegistryAccess(BuiltinRegistries.createWrapperLookup()); + protected static final int SUCCESS = com.mojang.brigadier.Command.SINGLE_SUCCESS; + protected static final MinecraftClient mc = MinecraftClient.getInstance(); + + private final String name, description; + private final List aliases; + + public Command(String name, String description, String... aliases) { + this.name = name; + this.description = description; + this.aliases = List.of(aliases); + } + + // métodos "helper" para reducir el "boilerplate" + protected static RequiredArgumentBuilder argument(final String name, final ArgumentType type) { + return RequiredArgumentBuilder.argument(name, type); + } + + protected static LiteralArgumentBuilder literal(final String name) { + return LiteralArgumentBuilder.literal(name); + } + + // para registrar comandos + public final void registerTo(CommandDispatcher dispatcher) { + register(dispatcher, name); + for (String alias : aliases) register(dispatcher, alias); + } + + public void register(CommandDispatcher dispatcher, String name) { + LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal(name); + build(builder); + dispatcher.register(builder); + } + + public abstract void build(LiteralArgumentBuilder builder); + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public List getAliases() { + return aliases; + } + + public String toString() { + return commandManager.getPrefix() + name; + } + + public String toString(String... args) { + StringBuilder sb = new StringBuilder(toString()); + for (String arg : args) sb.append(" ").append(arg); + return sb.toString(); + } +} diff --git a/src/main/java/me/retucio/camtweaks/command/CommandManager.java b/src/main/java/me/retucio/camtweaks/command/CommandManager.java new file mode 100644 index 0000000..6a53a7c --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/command/CommandManager.java @@ -0,0 +1,66 @@ +package me.retucio.camtweaks.command; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import me.retucio.camtweaks.command.commands.*; +import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandSource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Comparator; + +// registro de comandos +public class CommandManager { + + private String prefix = "$"; + + public static final List commands = new ArrayList<>(); + public static final CommandDispatcher dispatcher = new CommandDispatcher<>(); + + private static final MinecraftClient mc = MinecraftClient.getInstance(); + + public CommandManager() { + registerCommands(); + } + + public static void registerCommands() { + addCommand(new BindCommand()); + addCommand(new ToggleCommand()); + addCommand(new SendCommand()); + addCommand(new PrefixCommand()); + commands.sort(Comparator.comparing(Command::getName)); + } + + public static void addCommand(Command command) { + commands.removeIf(existing -> existing.getName().equals(command.getName())); + command.registerTo(dispatcher); + commands.add(command); + } + + public static void dispatch(String message) throws CommandSyntaxException { + dispatcher.execute(message, mc.getNetworkHandler().getCommandSource()); + } + + public static Command getCommandByName(String name) { + for (Command command : commands) + if (command.getName().equals(name)) + return command; + return null; + } + + public T getCommandByClass(Class clazz) { + for (Command command : commands) + if (clazz.isInstance(command)) + return clazz.cast(command); + return null; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } +} diff --git a/src/main/java/me/retucio/camtweaks/command/args/ModuleArgumentType.java b/src/main/java/me/retucio/camtweaks/command/args/ModuleArgumentType.java new file mode 100644 index 0000000..94ef217 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/command/args/ModuleArgumentType.java @@ -0,0 +1,49 @@ +package me.retucio.camtweaks.command.args; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import me.retucio.camtweaks.module.Module; +import net.minecraft.command.CommandSource; +import net.minecraft.text.Text; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +import static me.retucio.camtweaks.CameraTweaks.moduleManager; + +public class ModuleArgumentType implements ArgumentType { + + public static final ModuleArgumentType INSTANCE = new ModuleArgumentType(); + private static final DynamicCommandExceptionType unknownModuleE = new DynamicCommandExceptionType( + name -> Text.literal("módulo \"" + name + "\" no encontrado")); + + private static final Collection examples = moduleManager.getModules() + .stream().limit(3).map(Module::getName).toList(); + + @Override + public Module parse(StringReader reader) throws CommandSyntaxException { + String argument = reader.readString(); + Module module = moduleManager.getModuleByName(argument); + if (module == null) throw unknownModuleE.create(argument); + return module; + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return CommandSource.suggestMatching(moduleManager.getModules().stream().map(Module::getName), builder); + } + + @Override + public Collection getExamples() { + return examples; + } + + public static Module get(CommandContext ctx) { + return ctx.getArgument("módulo", Module.class); + } +} diff --git a/src/main/java/me/retucio/camtweaks/command/commands/BindCommand.java b/src/main/java/me/retucio/camtweaks/command/commands/BindCommand.java new file mode 100644 index 0000000..1fe65e4 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/command/commands/BindCommand.java @@ -0,0 +1,55 @@ +package me.retucio.camtweaks.command.commands; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import me.retucio.camtweaks.command.Command; +import me.retucio.camtweaks.command.args.ModuleArgumentType; +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.module.settings.KeySetting; +import me.retucio.camtweaks.util.ChatUtil; +import me.retucio.camtweaks.util.KeyUtil; +import net.minecraft.client.MinecraftClient; +import net.minecraft.command.CommandSource; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import static me.retucio.camtweaks.CameraTweaks.moduleManager; + +public class BindCommand extends Command { + + // The module currently waiting for a key press + private static Module listeningModule = null; + + public BindCommand() { + super("bind", "asigna una tecla a un módulo", "keybind"); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder + .then(argument("módulo", ModuleArgumentType.INSTANCE) + .executes(ctx -> { + Module module = ctx.getArgument("módulo", Module.class); + + listeningModule = module; + + ChatUtil.info("presiona una tecla para asignarla al módulo " + module.getName()); + return SUCCESS; + }) + ); + } + + public static void onKeyPress(int key) { + if (listeningModule != null) { + KeySetting bind = listeningModule.getBind(); + if (bind != null) + bind.setKey(key); + + ChatUtil.info( + Text.of("la tecla " + Formatting.AQUA + KeyUtil.getKeyName(key) + Formatting.RESET + + " ha sido asignada al módulo " + Formatting.GREEN + listeningModule.getName()) + ); + + listeningModule = null; + } + } +} diff --git a/src/main/java/me/retucio/camtweaks/command/commands/PrefixCommand.java b/src/main/java/me/retucio/camtweaks/command/commands/PrefixCommand.java new file mode 100644 index 0000000..4ea0c09 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/command/commands/PrefixCommand.java @@ -0,0 +1,28 @@ +package me.retucio.camtweaks.command.commands; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import me.retucio.camtweaks.command.Command; +import me.retucio.camtweaks.ui.settings.ClickGUISettingsFrame; +import me.retucio.camtweaks.util.ChatUtil; +import net.minecraft.command.CommandSource; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +public class PrefixCommand extends Command { + + public PrefixCommand() { + super("prefijo", "cambia el prefijo de los comandos", "prefix"); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder + .then(argument("prefijo", StringArgumentType.word()).executes(ctx -> { + String prefix = ctx.getArgument("prefijo", String.class); + ClickGUISettingsFrame.guiSettings.commandPrefix.setValue(prefix); + ChatUtil.info(Text.literal("prefijo cambiado a " + Formatting.AQUA + prefix)); + return SUCCESS; + })); + } +} diff --git a/src/main/java/me/retucio/camtweaks/command/commands/SendCommand.java b/src/main/java/me/retucio/camtweaks/command/commands/SendCommand.java new file mode 100644 index 0000000..30c548a --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/command/commands/SendCommand.java @@ -0,0 +1,26 @@ +package me.retucio.camtweaks.command.commands; + +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import me.retucio.camtweaks.command.Command; +import net.minecraft.command.CommandSource; +import net.minecraft.text.Text; + +public class SendCommand extends Command { + + public SendCommand() { + super("enviar", "manda un mensaje en el chat", "send"); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder + .then(argument("mensaje", StringArgumentType.greedyString()) + .executes(ctx -> { + if (mc.player == null) return 0; + mc.player.sendMessage(Text.of(ctx.getArgument("mensaje", String.class)), false); + return SUCCESS; + }) + ); + } +} diff --git a/src/main/java/me/retucio/camtweaks/command/commands/ToggleCommand.java b/src/main/java/me/retucio/camtweaks/command/commands/ToggleCommand.java new file mode 100644 index 0000000..046a114 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/command/commands/ToggleCommand.java @@ -0,0 +1,58 @@ +package me.retucio.camtweaks.command.commands; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import me.retucio.camtweaks.command.Command; +import me.retucio.camtweaks.command.args.ModuleArgumentType; +import me.retucio.camtweaks.module.Module; +import net.minecraft.command.CommandSource; + +import static me.retucio.camtweaks.CameraTweaks.moduleManager; + +// porque usar el ratón es más lento que escribir, a menos que tengas toque con el teclado como yo +public class ToggleCommand extends Command { + + public ToggleCommand() { + super("alternar", "enciende / apaga módulos", "toggle"); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder + .then(literal("todos") + .then(literal("on") + .executes(ctx -> { + moduleManager.getModules().forEach(module -> { + if (!module.isEnabled()) module.toggle(); + }); return SUCCESS; + }) + ) + .then(literal("off") + .executes(ctx -> { + moduleManager.getEnabledModules().forEach(Module::toggle); + return SUCCESS; + }) + ) + ) + .then(argument("módulo", ModuleArgumentType.INSTANCE) + .executes(ctx -> { + Module module = ctx.getArgument("módulo", Module.class); + module.toggle(); + return SUCCESS; + }) + .then(literal("on") + .executes(ctx -> { + Module module = ctx.getArgument("módulo", Module.class); + module.setEnabled(true); + return SUCCESS; + }) + ) + .then(literal("off") + .executes(ctx -> { + Module module = ctx.getArgument("módulo", Module.class); + module.setEnabled(false); + return SUCCESS; + }) + ) + ); + } +} diff --git a/src/main/java/me/retucio/camtweaks/event/Event.java b/src/main/java/me/retucio/camtweaks/event/Event.java new file mode 100644 index 0000000..0cc1277 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/Event.java @@ -0,0 +1,14 @@ +package me.retucio.camtweaks.event; + +// evento base, cancelable +public class Event { + private boolean cancelled; + + public boolean isCancelled() { + return cancelled; + } + + public void cancel() { + cancelled = true; + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/event/EventBus.java b/src/main/java/me/retucio/camtweaks/event/EventBus.java new file mode 100644 index 0000000..403b02f --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/EventBus.java @@ -0,0 +1,55 @@ +package me.retucio.camtweaks.event; + +import java.lang.reflect.Method; +import java.util.*; + +// al llamar un evento, se guarda en el bus de eventos, de donde luego se puede detectar si ese evento ha sucedido +public class EventBus { + private final Map, List> listeners = new HashMap<>(); + + private record ListenerMethod(Object instance, Method method) {} + + public void register(Object listener) { + for (Method method : listener.getClass().getDeclaredMethods()) { + if (method.isAnnotationPresent(Subscribe.class)) { + if (method.getParameterCount() != 1) continue; // must take 1 param + Class eventType = method.getParameterTypes()[0]; + method.setAccessible(true); + + listeners.computeIfAbsent(eventType, k -> new ArrayList<>()) + .add(new ListenerMethod(listener, method)); + } + } + } + + public void unregister(Object listener) { + for (List list : listeners.values()) { + list.removeIf(lm -> lm.instance == listener); + } + } + + public T post(T event) { + List list = listeners.get(event.getClass()); + if (list != null) { + for (ListenerMethod lm : list) { + try { + lm.method.invoke(lm.instance, event); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + return event; + } + + public boolean isRegistered(Object listener) { + for (List list : listeners.values()) { + for (ListenerMethod lm : list) { + if (lm.instance == listener) { + return true; + } + } + } + return false; + } +} diff --git a/src/main/java/me/retucio/camtweaks/event/Subscribe.java b/src/main/java/me/retucio/camtweaks/event/Subscribe.java new file mode 100644 index 0000000..17dabfb --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/Subscribe.java @@ -0,0 +1,8 @@ +package me.retucio.camtweaks.event; + +import java.lang.annotation.*; + +// anotación para registar un "listener", que escucha cuando se llama un evento +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Subscribe {} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/event/events/ClientClickEvent.java b/src/main/java/me/retucio/camtweaks/event/events/ClientClickEvent.java new file mode 100644 index 0000000..fd4028f --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/events/ClientClickEvent.java @@ -0,0 +1,22 @@ +package me.retucio.camtweaks.event.events; + +import net.minecraft.text.ClickEvent; + +public class ClientClickEvent implements ClickEvent { + + private final String value; + + public ClientClickEvent(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public Action getAction() { + return Action.RUN_COMMAND; + } + +} diff --git a/src/main/java/me/retucio/camtweaks/event/events/GetFOVEvent.java b/src/main/java/me/retucio/camtweaks/event/events/GetFOVEvent.java new file mode 100644 index 0000000..9fd3b80 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/events/GetFOVEvent.java @@ -0,0 +1,20 @@ +package me.retucio.camtweaks.event.events; + +import me.retucio.camtweaks.event.Event; + +public class GetFOVEvent extends Event { + + private float fov; + + public GetFOVEvent(float fov) { + this.fov = fov; + } + + public float getFov() { + return fov; + } + + public void setFov(float fov) { + this.fov = fov; + } +} diff --git a/src/main/java/me/retucio/camtweaks/event/events/HeldItemRendererEvent.java b/src/main/java/me/retucio/camtweaks/event/events/HeldItemRendererEvent.java new file mode 100644 index 0000000..5c467dc --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/events/HeldItemRendererEvent.java @@ -0,0 +1,32 @@ +package me.retucio.camtweaks.event.events; + +import me.retucio.camtweaks.event.Event; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.Hand; + +public class HeldItemRendererEvent extends Event { + + private MatrixStack matrices; + private Hand hand; + + public HeldItemRendererEvent(MatrixStack matrices, Hand hand) { + this.matrices = matrices; + this.hand = hand; + } + + public MatrixStack getMatrices() { + return matrices; + } + + public void setMatrices(MatrixStack matrices) { + this.matrices = matrices; + } + + public Hand getHand() { + return hand; + } + + public void setHand(Hand hand) { + this.hand = hand; + } +} diff --git a/src/main/java/me/retucio/camtweaks/event/events/KeyEvent.java b/src/main/java/me/retucio/camtweaks/event/events/KeyEvent.java new file mode 100644 index 0000000..5117c90 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/events/KeyEvent.java @@ -0,0 +1,27 @@ +package me.retucio.camtweaks.event.events; + +import me.retucio.camtweaks.event.Event; + +// se genera en KeyboardMixin +public class KeyEvent extends Event { + + private final int key, scancode, action; + + public KeyEvent(int key, int scancode, int action) { + this.key = key; + this.scancode = scancode; + this.action = action; + } + + public int getKey() { + return key; + } + + public int getAction() { + return action; + } + + public int getScancode() { + return scancode; + } +} diff --git a/src/main/java/me/retucio/camtweaks/event/events/MouseClickEvent.java b/src/main/java/me/retucio/camtweaks/event/events/MouseClickEvent.java new file mode 100644 index 0000000..92524fd --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/events/MouseClickEvent.java @@ -0,0 +1,22 @@ +package me.retucio.camtweaks.event.events; + +import me.retucio.camtweaks.event.Event; + +// se genera en MouseMixin +public class MouseClickEvent extends Event { + + private final int action, button; + + public MouseClickEvent(int action, int button) { + this.action = action; + this.button = button; + } + + public int getAction() { + return action; + } + + public int getButton() { + return button; + } +} diff --git a/src/main/java/me/retucio/camtweaks/event/events/MouseScrollEvent.java b/src/main/java/me/retucio/camtweaks/event/events/MouseScrollEvent.java new file mode 100644 index 0000000..db61a20 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/events/MouseScrollEvent.java @@ -0,0 +1,30 @@ +package me.retucio.camtweaks.event.events; + +import me.retucio.camtweaks.event.Event; + +// se genera en MouseMixin +public class MouseScrollEvent extends Event { + + private double horizontal, vertical; + + public MouseScrollEvent(double horizontal, double vertical) { + this.horizontal = horizontal; + this.vertical = vertical; + } + + public double getHorizontal() { + return horizontal; + } + + public void setHorizontal(double horizontal) { + this.horizontal = horizontal; + } + + public double getVertical() { + return vertical; + } + + public void setVertical(double vertical) { + this.vertical = vertical; + } +} diff --git a/src/main/java/me/retucio/camtweaks/event/events/PerspectiveChangeEvent.java b/src/main/java/me/retucio/camtweaks/event/events/PerspectiveChangeEvent.java new file mode 100644 index 0000000..b9e4283 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/events/PerspectiveChangeEvent.java @@ -0,0 +1,22 @@ +package me.retucio.camtweaks.event.events; + +import me.retucio.camtweaks.event.Event; +import net.minecraft.client.option.Perspective; + +// se genera en GameOptionsMixin +public class PerspectiveChangeEvent extends Event { + + private Perspective perspective; + + public PerspectiveChangeEvent(Perspective perspective) { + this.perspective = perspective; + } + + public Perspective getPerspective() { + return perspective; + } + + public void setPerspective(Perspective perspective) { + this.perspective = perspective; + } +} diff --git a/src/main/java/me/retucio/camtweaks/event/events/ReceiveMessageEvent.java b/src/main/java/me/retucio/camtweaks/event/events/ReceiveMessageEvent.java new file mode 100644 index 0000000..9a84a4f --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/events/ReceiveMessageEvent.java @@ -0,0 +1,43 @@ +package me.retucio.camtweaks.event.events; + +import me.retucio.camtweaks.event.Event; +import net.minecraft.client.gui.hud.MessageIndicator; +import net.minecraft.text.Text; + +public class ReceiveMessageEvent extends Event { + + private Text text; + private MessageIndicator indicator; + private int id; + private boolean modified = false; + + public ReceiveMessageEvent(Text text, MessageIndicator indicator, int id) { + this.text = text; + this.indicator = indicator; + this.id = id; + } + + public Text getMessage() { + return text; + } + + public MessageIndicator getIndicator() { + return indicator; + } + + public void setMessage(Text message) { + this.text = message; + this.modified = true; + } + + public void setIndicator(MessageIndicator indicator) { + this.indicator = indicator; + this.modified = true; + } + + public boolean wasModified() { + return modified; + } + + +} diff --git a/src/main/java/me/retucio/camtweaks/event/events/RenderArmEvent.java b/src/main/java/me/retucio/camtweaks/event/events/RenderArmEvent.java new file mode 100644 index 0000000..e9c9ae8 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/events/RenderArmEvent.java @@ -0,0 +1,32 @@ +package me.retucio.camtweaks.event.events; + +import me.retucio.camtweaks.event.Event; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.Hand; + +public class RenderArmEvent extends Event { + + private MatrixStack matrices; + private Hand hand; + + public RenderArmEvent(MatrixStack matrices, Hand hand) { + this.matrices = matrices; + this.hand = hand; + } + + public MatrixStack getMatrices() { + return matrices; + } + + public void setMatrices(MatrixStack matrices) { + this.matrices = matrices; + } + + public Hand getHand() { + return hand; + } + + public void setHand(Hand hand) { + this.hand = hand; + } +} diff --git a/src/main/java/me/retucio/camtweaks/event/events/RenderEvent.java b/src/main/java/me/retucio/camtweaks/event/events/RenderEvent.java new file mode 100644 index 0000000..bba6f65 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/events/RenderEvent.java @@ -0,0 +1,6 @@ +package me.retucio.camtweaks.event.events; + +import me.retucio.camtweaks.event.Event; + +public class RenderEvent extends Event { +} diff --git a/src/main/java/me/retucio/camtweaks/event/events/SendMessageEvent.java b/src/main/java/me/retucio/camtweaks/event/events/SendMessageEvent.java new file mode 100644 index 0000000..0fa6d61 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/event/events/SendMessageEvent.java @@ -0,0 +1,20 @@ +package me.retucio.camtweaks.event.events; + +import me.retucio.camtweaks.event.Event; + +public class SendMessageEvent extends Event { + + private String message; + + public SendMessageEvent(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/CameraMixin.java b/src/main/java/me/retucio/camtweaks/mixin/CameraMixin.java new file mode 100644 index 0000000..fd50dbb --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/CameraMixin.java @@ -0,0 +1,29 @@ +package me.retucio.camtweaks.mixin; + +import me.retucio.camtweaks.CameraTweaks; +import me.retucio.camtweaks.module.modules.Perspective; +import net.minecraft.client.render.Camera; +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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import static me.retucio.camtweaks.CameraTweaks.moduleManager; + +@Mixin(Camera.class) +public abstract class CameraMixin { + + @ModifyVariable(method = "clipToSpace", at = @At("HEAD"), ordinal = 0, argsOnly = true) + private float modifyClipToSpace(float distance) { + Perspective perspective = moduleManager.getModuleByClass(Perspective.class); + return perspective.isEnabled() ? (float) perspective.getDistance() : distance; + } + + @Inject(method = "clipToSpace", at = @At("HEAD"), cancellable = true) + private void onClipToSpace(float distance, CallbackInfoReturnable cir) { + if (moduleManager.getModuleByClass(Perspective.class).isEnabled() + && moduleManager.getModuleByClass(Perspective.class).clip.isEnabled()) + cir.setReturnValue(distance); + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/ChatHudLineMixin.java b/src/main/java/me/retucio/camtweaks/mixin/ChatHudLineMixin.java new file mode 100644 index 0000000..5eaf08e --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/ChatHudLineMixin.java @@ -0,0 +1,38 @@ +package me.retucio.camtweaks.mixin; + +import com.mojang.authlib.GameProfile; +import me.retucio.camtweaks.util.interfaces.IChatHudLine; +import net.minecraft.client.gui.hud.ChatHudLine; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(ChatHudLine.class) +public class ChatHudLineMixin implements IChatHudLine { + + @Shadow @Final + private Text content; + + @Unique + private int id; + + @Unique + private GameProfile sender; + + @Override + public String smegma$getText() { return content.toString(); } + + @Override + public int smegma$getId() { return id; } + + @Override + public void smegma$setId(int id) { this.id = id; } + + @Override + public GameProfile smegma$getSender() { return sender; } + + @Override + public void smegma$setSender(GameProfile profile) { this.sender = profile; } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/ChatHudLineVisibleMixin.java b/src/main/java/me/retucio/camtweaks/mixin/ChatHudLineVisibleMixin.java new file mode 100644 index 0000000..1e99861 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/ChatHudLineVisibleMixin.java @@ -0,0 +1,55 @@ +package me.retucio.camtweaks.mixin; + +import com.mojang.authlib.GameProfile; +import me.retucio.camtweaks.util.interfaces.IChatHudLineVisible; +import net.minecraft.client.gui.hud.ChatHudLine; +import net.minecraft.text.OrderedText; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(ChatHudLine.Visible.class) +public class ChatHudLineVisibleMixin implements IChatHudLineVisible { + + @Shadow @Final + private OrderedText content; + + @Unique + private int id; + + @Unique + private GameProfile sender; + + @Unique + private boolean startOfEntry; + + @Override + public String smegma$getText() { + StringBuilder sb = new StringBuilder(); + + content.accept((index, style, codePoint) -> { + sb.appendCodePoint(codePoint); + return true; + }); + return sb.toString(); + } + + @Override + public int smegma$getId() { return id; } + + @Override + public void smegma$setId(int id) { this.id = id; } + + @Override + public GameProfile smegma$getSender() { return sender; } + + @Override + public void smegma$setSender(GameProfile profile) { this.sender = profile; } + + @Override + public boolean smegma$isStartOfEntry() { return startOfEntry; } + + @Override + public void smegma$setStartOfEntry(boolean start) { this.startOfEntry = start; } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/mixin/ChatHudMixin.java b/src/main/java/me/retucio/camtweaks/mixin/ChatHudMixin.java new file mode 100644 index 0000000..4bfc00c --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/ChatHudMixin.java @@ -0,0 +1,194 @@ +package me.retucio.camtweaks.mixin; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.ModifyReceiver; +import com.llamalad7.mixinextras.sugar.Local; +import me.retucio.camtweaks.event.events.ReceiveMessageEvent; +import me.retucio.camtweaks.module.modules.BetterChat; +import me.retucio.camtweaks.util.interfaces.IChatHud; +import me.retucio.camtweaks.util.interfaces.IChatHudLine; +import me.retucio.camtweaks.util.interfaces.IChatHudLineVisible; +import me.retucio.camtweaks.util.interfaces.IMessageHandler; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.hud.ChatHud; +import net.minecraft.client.gui.hud.ChatHudLine; +import net.minecraft.client.gui.hud.MessageIndicator; +import net.minecraft.network.message.MessageSignatureData; +import net.minecraft.text.OrderedText; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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 java.util.List; + +import static me.retucio.camtweaks.CameraTweaks.*; + +@Mixin(ChatHud.class) +public abstract class ChatHudMixin implements IChatHud { + + @Unique + private boolean skipOnAddMessage; + + @Unique + private int nextId; + + @Unique @Final + private BetterChat betterChat = moduleManager.getModuleByClass(BetterChat.class); + + @Shadow @Final + private List messages; + + @Shadow @Final + private List visibleMessages; + + @Shadow + public abstract void addMessage(Text message); + + @Shadow + public abstract void addMessage(Text message, @Nullable MessageSignatureData signatureData, @Nullable MessageIndicator indicator); + + + + // métodos relacionados a las interfaces IChatHud, IChatHudLine, IChadHudLineVisible y IMessageHandler + + @Override + public void smegma$add(Text message, int id) { + nextId = id; + addMessage(message); + nextId = 0; + } + + @Inject(method = "addVisibleMessage", at = @At(value = "INVOKE", target = "Ljava/util/List;add(ILjava/lang/Object;)V", shift = At.Shift.AFTER)) + private void onAddMessageAfterNewChatHudLineVisible(ChatHudLine message, CallbackInfo ci) { + ((IChatHudLine) (Object) visibleMessages.getFirst()).smegma$setId(nextId); + } + + @Inject(method = "addMessage(Lnet/minecraft/client/gui/hud/ChatHudLine;)V", at = @At(value = "INVOKE", target = "Ljava/util/List;add(ILjava/lang/Object;)V", shift = At.Shift.AFTER)) + private void onAddMessageAfterNewChatHudLine(ChatHudLine message, CallbackInfo ci) { + ((IChatHudLine) (Object) messages.getFirst()).smegma$setId(nextId); + } + + @SuppressWarnings("DataFlowIssue") + @ModifyExpressionValue(method = "addVisibleMessage", at = @At(value = "NEW", target = "(ILnet/minecraft/text/OrderedText;Lnet/minecraft/client/gui/hud/MessageIndicator;Z)Lnet/minecraft/client/gui/hud/ChatHudLine$Visible;")) + private ChatHudLine.Visible onAddMessage_modifyChatHudLineVisible(ChatHudLine.Visible line, @Local(ordinal = 1) int j) { + IMessageHandler handler = (IMessageHandler) mc.getMessageHandler(); + if (handler == null) return line; + + IChatHudLineVisible iLine = (IChatHudLineVisible) (Object) line; + + iLine.smegma$setSender(handler.smegma$getSender()); + iLine.smegma$setStartOfEntry(j == 0); + + return line; + } + + @ModifyExpressionValue(method = "addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)V", at = @At(value = "NEW", target = "(ILnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)Lnet/minecraft/client/gui/hud/ChatHudLine;")) + private ChatHudLine onAddMessage_modifyChatHudLine(ChatHudLine line) { + IMessageHandler handler = (IMessageHandler) mc.getMessageHandler(); + if (handler == null) return line; + + ((IChatHudLine) (Object) line).smegma$setSender(handler.smegma$getSender()); + return line; + } + + + + // modificar contenido del mensaje + @Inject(at = @At("HEAD"), method = "addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)V", cancellable = true) + private void onAddMessage(Text message, MessageSignatureData signatureData, MessageIndicator indicator, CallbackInfo ci) { + if (skipOnAddMessage) return; + + ReceiveMessageEvent event = EVENT_BUS.post(new ReceiveMessageEvent(message, indicator, nextId)); + + if (event.isCancelled()) { + ci.cancel(); + } else { + visibleMessages.removeIf(msg -> ((IChatHudLine) (Object) msg).smegma$getId() == nextId && nextId != 0); + + for (int i = messages.size() - 1; i > -1; i--) { + if (((IChatHudLine) (Object) messages.get(i)).smegma$getId() == nextId && nextId != 0) { + messages.remove(i); + betterChat.removeLine(i); + } + } + + if (event.wasModified()) { + ci.cancel(); + + skipOnAddMessage = true; + addMessage(event.getMessage(), signatureData, event.getIndicator()); + skipOnAddMessage = false; + } + } + } + + + + // cabezas + + @ModifyExpressionValue(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/MathHelper;ceil(F)I")) + private int onRender_modifyWidth(int width) { + return (betterChat.isEnabled() && betterChat.showHeads.isEnabled()) ? width + 10 : width; + } + + @ModifyReceiver(method = "method_71991", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/DrawContext;drawTextWithShadow(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/OrderedText;III)V")) + private DrawContext onRender_beforeDrawTextWithShadow(DrawContext context, TextRenderer textRenderer, OrderedText text, int x, int y, int color, @Local(argsOnly = true) ChatHudLine.Visible line) { + betterChat.beforeDrawMessage(context, line, y, color); + return context; + } + + @Inject(method = "method_71991", at = @At("TAIL")) + private void onRender_afterDrawTextWithShadow(int i, DrawContext context, float f, int j, int k, int l, ChatHudLine.Visible visible, int m, float g, CallbackInfo info) { + betterChat.afterDrawMessage(context); + } + + + + // historial (registro) del chat + + @ModifyExpressionValue(method = "addMessage(Lnet/minecraft/client/gui/hud/ChatHudLine;)V", at = @At(value = "CONSTANT", args = "intValue=100")) + private int maxLength(int size) { + if (!betterChat.isEnabled()) return size; + return size + betterChat.chatHistoryExtraLength.getIntValue(); + } + + @ModifyExpressionValue(method = "addVisibleMessage", at = @At(value = "CONSTANT", args = "intValue=100")) + private int maxLengthVisible(int size) { + if (!betterChat.isEnabled()) return size; + return size + betterChat.chatHistoryExtraLength.getIntValue(); + } + + @Inject(method = "addVisibleMessage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/ChatHud;isChatFocused()Z")) + private void onBreakChatMessageLines(ChatHudLine message, CallbackInfo ci, @Local List list) { + betterChat.lines.addFirst(list.size()); + } + + @Inject(method = "addMessage(Lnet/minecraft/client/gui/hud/ChatHudLine;)V", at = @At(value = "INVOKE", target = "Ljava/util/List;remove(I)Ljava/lang/Object;")) + private void onRemoveMessage(ChatHudLine message, CallbackInfo ci) { + int extra = betterChat.chatHistoryExtraLength.getIntValue(); + int size = betterChat.lines.size(); + + while (size > 100 + extra) { + betterChat.lines.removeLast(); + size--; + } + } + + @Inject(method = "clear", at = @At("HEAD")) + private void onClear(boolean clearHistory, CallbackInfo ci) { + betterChat.lines.clear(); + } + + @Inject(method = "refresh", at = @At("HEAD")) + private void onRefresh(CallbackInfo ci) { + betterChat.lines.clear(); + } + +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/mixin/ChatInputSuggestorMixin.java b/src/main/java/me/retucio/camtweaks/mixin/ChatInputSuggestorMixin.java new file mode 100644 index 0000000..679bc4b --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/ChatInputSuggestorMixin.java @@ -0,0 +1,63 @@ +package me.retucio.camtweaks.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.suggestion.Suggestions; +import me.retucio.camtweaks.command.CommandManager; +import net.minecraft.client.gui.screen.ChatInputSuggestor; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.command.CommandSource; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.concurrent.CompletableFuture; + +import static me.retucio.camtweaks.CameraTweaks.commandManager; +import static me.retucio.camtweaks.CameraTweaks.mc; + +@Mixin(ChatInputSuggestor.class) +public abstract class ChatInputSuggestorMixin { + + @Shadow + private ParseResults parse; + + @Shadow @Final + TextFieldWidget textField; + + @Shadow + private ChatInputSuggestor.SuggestionWindow window; + + @Shadow + boolean completingSuggestions; + + @Shadow + private CompletableFuture pendingSuggestions; + + @Shadow protected abstract void showCommandSuggestions(); + + @Inject(method = "refresh", at = @At(value = "INVOKE", target = "Lcom/mojang/brigadier/StringReader;canRead()Z", remap = false), cancellable = true) + private void onRefresh(CallbackInfo ci, @Local StringReader reader) { + String prefix = commandManager.getPrefix(); + int length = prefix.length(); + + if (reader.canRead(length) && reader.getString().startsWith(prefix, reader.getCursor())) { + reader.setCursor(reader.getCursor() + length); + + if (parse == null) this.parse = CommandManager.dispatcher.parse(reader, mc.getNetworkHandler().getCommandSource()); + + int cursor = textField.getCursor(); + if (cursor >= length && (window == null || !completingSuggestions)) { + pendingSuggestions = CommandManager.dispatcher.getCompletionSuggestions(parse, cursor); + pendingSuggestions.thenRun(() -> { + if (pendingSuggestions.isDone()) showCommandSuggestions(); + }); + } + ci.cancel(); + } + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/ChatScreenMixin.java b/src/main/java/me/retucio/camtweaks/mixin/ChatScreenMixin.java new file mode 100644 index 0000000..e4a1f0c --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/ChatScreenMixin.java @@ -0,0 +1,25 @@ +package me.retucio.camtweaks.mixin; + +import me.retucio.camtweaks.module.modules.BetterChat; +import net.minecraft.client.gui.screen.ChatScreen; +import net.minecraft.client.gui.widget.TextFieldWidget; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import static me.retucio.camtweaks.CameraTweaks.moduleManager; + +@Mixin(value = ChatScreen.class, priority = 1001) +public class ChatScreenMixin { + + @Shadow + protected TextFieldWidget chatField; + + @Inject(method = "init", at = @At(value = "RETURN")) + private void onInit(CallbackInfo info) { + BetterChat betterChat = moduleManager.getModuleByClass(BetterChat.class); + if (betterChat.isEnabled() && betterChat.noCharLimit.isEnabled()) chatField.setMaxLength(Integer.MAX_VALUE); + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/me/retucio/camtweaks/mixin/ClientPlayNetworkHandlerMixin.java new file mode 100644 index 0000000..e912bdf --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/ClientPlayNetworkHandlerMixin.java @@ -0,0 +1,49 @@ +package me.retucio.camtweaks.mixin; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import me.retucio.camtweaks.command.CommandManager; +import me.retucio.camtweaks.event.events.SendMessageEvent; +import me.retucio.camtweaks.util.ChatUtil; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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 static me.retucio.camtweaks.CameraTweaks.*; + +@Mixin(ClientPlayNetworkHandler.class) +public abstract class ClientPlayNetworkHandlerMixin { + + @Unique + private boolean ignoreChatMessage; + + @Shadow + public abstract void sendChatMessage(String content); + + @Inject(method = "sendChatMessage", at = @At("HEAD"), cancellable = true) + private void onSendMessage(String message, CallbackInfo ci) { + if (ignoreChatMessage) return; + if (!message.startsWith(commandManager.getPrefix())) { + SendMessageEvent event = EVENT_BUS.post(new SendMessageEvent(message)); + if (!event.isCancelled()) { + ignoreChatMessage = true; + sendChatMessage(event.getMessage()); + ignoreChatMessage = false; + } + ci.cancel(); + return; + }; + + try { + CommandManager.dispatch(message.substring(commandManager.getPrefix().length())); + } catch (CommandSyntaxException e) { + ChatUtil.error(e.getMessage()); + } + + mc.inGameHud.getChatHud().addToMessageHistory(message); + ci.cancel(); + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/GameOptionsMixin.java b/src/main/java/me/retucio/camtweaks/mixin/GameOptionsMixin.java new file mode 100644 index 0000000..198de98 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/GameOptionsMixin.java @@ -0,0 +1,22 @@ +package me.retucio.camtweaks.mixin; + +import me.retucio.camtweaks.CameraTweaks; +import me.retucio.camtweaks.event.events.PerspectiveChangeEvent; +import net.minecraft.client.option.GameOptions; +import net.minecraft.client.option.Perspective; +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; + +import static me.retucio.camtweaks.CameraTweaks.EVENT_BUS; + +@Mixin(GameOptions.class) +public abstract class GameOptionsMixin { + + @Inject(method = "setPerspective", at = @At("HEAD"), cancellable = true) + private void changePerspective(Perspective perspective, CallbackInfo ci) { + PerspectiveChangeEvent event = EVENT_BUS.post(new PerspectiveChangeEvent(perspective)); + if (event.isCancelled()) ci.cancel(); + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/GameRendererMixin.java b/src/main/java/me/retucio/camtweaks/mixin/GameRendererMixin.java new file mode 100644 index 0000000..1d63310 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/GameRendererMixin.java @@ -0,0 +1,40 @@ +package me.retucio.camtweaks.mixin; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import me.retucio.camtweaks.CameraTweaks; +import me.retucio.camtweaks.event.events.GetFOVEvent; +import me.retucio.camtweaks.event.events.RenderEvent; +import me.retucio.camtweaks.module.modules.Zoom; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.render.RenderTickCounter; +import org.joml.Matrix4f; +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 static me.retucio.camtweaks.CameraTweaks.EVENT_BUS; + +@Mixin(GameRenderer.class) +public abstract class GameRendererMixin { + + @Unique + Zoom zoom = CameraTweaks.moduleManager.getModuleByClass(Zoom.class); + + @ModifyReturnValue(method = "getFov", at = @At("RETURN")) + private float modifyFov(float original) { + return EVENT_BUS.post(new GetFOVEvent(original)).getFov(); + } + +// @Inject(method = "render", at = @At("HEAD")) +// private void onRender(RenderTickCounter tickCounter, boolean tick, CallbackInfo ci) { +// CameraTweaks.INSTANCE.EVENT_BUS.post(new RenderEvent()); +// } + + @Inject(method = "renderHand", at = @At("HEAD"), cancellable = true) + private void renderHand(float tickProgress, boolean sleeping, Matrix4f positionMatrix, CallbackInfo ci) { + if (zoom.isEnabled() && !zoom.showHands.isEnabled()) + ci.cancel(); + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/HeldItemRendererMixin.java b/src/main/java/me/retucio/camtweaks/mixin/HeldItemRendererMixin.java new file mode 100644 index 0000000..7c76ad4 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/HeldItemRendererMixin.java @@ -0,0 +1,49 @@ +package me.retucio.camtweaks.mixin; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import me.retucio.camtweaks.event.events.HeldItemRendererEvent; +import me.retucio.camtweaks.event.events.RenderArmEvent; +import me.retucio.camtweaks.module.modules.HandView; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.item.HeldItemRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Arm; +import net.minecraft.util.Hand; +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 static me.retucio.camtweaks.CameraTweaks.EVENT_BUS; +import static me.retucio.camtweaks.CameraTweaks.moduleManager; + +@Mixin(HeldItemRenderer.class) +public abstract class HeldItemRendererMixin { + + @Unique + HandView handView = moduleManager.getModuleByClass(HandView.class); + + @Inject(method = "renderFirstPersonItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ItemDisplayContext;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", shift = At.Shift.BEFORE)) + private void onRenderItem(AbstractClientPlayerEntity player, float tickDelta, float pitch, Hand hand, float swingProgress, ItemStack item, float equipProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) { + EVENT_BUS.post(new HeldItemRendererEvent(matrices, hand)); + } + + @ModifyReturnValue(method = "shouldSkipHandAnimationOnSwap", at = @At("RETURN")) + private boolean modifySkipSwapAnimation(boolean original) { + return original || (handView.isEnabled() && handView.skipSwapping.isEnabled()); + } + + @Inject(method = "applyEatOrDrinkTransformation", at = @At(value = "INVOKE", target = "Ljava/lang/Math;pow(DD)D", shift = At.Shift.BEFORE), cancellable = true) + private void cancelTransformations(MatrixStack matrices, float tickDelta, Arm arm, ItemStack stack, PlayerEntity player, CallbackInfo ci) { + if (handView.isEnabled() && handView.noFood.isEnabled()) ci.cancel(); + } + + @Inject(method = "renderFirstPersonItem", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/HeldItemRenderer;renderArmHoldingItem(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IFFLnet/minecraft/util/Arm;)V")) + private void onRenderArm(AbstractClientPlayerEntity player, float tickDelta, float pitch, Hand hand, float swingProgress, ItemStack item, float equipProgress, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, CallbackInfo ci) { + EVENT_BUS.post(new RenderArmEvent(matrices, hand)); + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/InGameHudMixin.java b/src/main/java/me/retucio/camtweaks/mixin/InGameHudMixin.java new file mode 100644 index 0000000..2b044c5 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/InGameHudMixin.java @@ -0,0 +1,28 @@ +package me.retucio.camtweaks.mixin; + +import me.retucio.camtweaks.module.modules.BetterChat; +import me.retucio.camtweaks.ui.HUD; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.hud.InGameHud; +import net.minecraft.client.render.RenderTickCounter; +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; + +import static me.retucio.camtweaks.CameraTweaks.moduleManager; + +@Mixin(InGameHud.class) +public abstract class InGameHudMixin { + + @Inject(method = "render", at = @At("RETURN"), cancellable = true) + private void renderHUD(DrawContext ctx, RenderTickCounter tc, CallbackInfo ci) { + HUD.render(ctx, tc); + } + + @Inject(method = "clear", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/ChatHud;clear(Z)V"), cancellable = true) + private void onClear(CallbackInfo ci) { + BetterChat betterChat = moduleManager.getModuleByClass(BetterChat.class); + if (betterChat.isEnabled() && betterChat.logger.isEnabled()) ci.cancel(); + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/KeyboardMixin.java b/src/main/java/me/retucio/camtweaks/mixin/KeyboardMixin.java new file mode 100644 index 0000000..08ad4d4 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/KeyboardMixin.java @@ -0,0 +1,23 @@ +package me.retucio.camtweaks.mixin; + +import me.retucio.camtweaks.CameraTweaks; +import me.retucio.camtweaks.event.events.KeyEvent; +import net.minecraft.client.Keyboard; +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; + +import static me.retucio.camtweaks.CameraTweaks.EVENT_BUS; + +@Mixin(Keyboard.class) +public abstract class KeyboardMixin { + + @Inject(method = "onKey", at = @At("HEAD"), cancellable = true) + private void onKeyPress(long window, int key, int scancode, int action, int modifiers, CallbackInfo ci) { + CameraTweaks.INSTANCE.onKeyPress(key, action); + + KeyEvent event = EVENT_BUS.post(new KeyEvent(key, scancode, action)); + if (event.isCancelled()) ci.cancel(); + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/LightmapTextureManagerMixin.java b/src/main/java/me/retucio/camtweaks/mixin/LightmapTextureManagerMixin.java new file mode 100644 index 0000000..d4bfddf --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/LightmapTextureManagerMixin.java @@ -0,0 +1,37 @@ +package me.retucio.camtweaks.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.textures.GpuTexture; +import me.retucio.camtweaks.module.modules.Fullbright; +import net.minecraft.client.render.LightmapTextureManager; +import net.minecraft.util.math.ColorHelper; +import net.minecraft.util.profiler.Profiler; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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 static me.retucio.camtweaks.CameraTweaks.moduleManager; + +@Mixin(LightmapTextureManager.class) +public abstract class LightmapTextureManagerMixin { + + @Unique + Fullbright fullbright = moduleManager.getModuleByClass(Fullbright.class); + + @Shadow @Final + private GpuTexture glTexture; + + @Inject(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiler/Profiler;push(Ljava/lang/String;)V", shift = At.Shift.AFTER), cancellable = true) + private void update(float tickProgress, CallbackInfo ci, @Local Profiler profiler) { + if (fullbright.isEnabled() && fullbright.mode.is(Fullbright.Modes.GAMMA)) { + RenderSystem.getDevice().createCommandEncoder().clearColorTexture(glTexture, ColorHelper.getArgb(255, 255, 255, 255)); + profiler.pop(); + ci.cancel(); + } + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/MessageHandlerMixin.java b/src/main/java/me/retucio/camtweaks/mixin/MessageHandlerMixin.java new file mode 100644 index 0000000..09029d7 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/MessageHandlerMixin.java @@ -0,0 +1,37 @@ +package me.retucio.camtweaks.mixin; + +import com.mojang.authlib.GameProfile; +import me.retucio.camtweaks.util.interfaces.IMessageHandler; +import net.minecraft.client.network.message.MessageHandler; +import net.minecraft.network.message.MessageType; +import net.minecraft.network.message.SignedMessage; +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.CallbackInfoReturnable; + +import java.time.Instant; + +@Mixin(MessageHandler.class) +public abstract class MessageHandlerMixin implements IMessageHandler { + + @Unique + private GameProfile sender; + + @Inject(method = "processChatMessageInternal", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/ChatHud;addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)V", shift = At.Shift.BEFORE)) + private void onProcessChatMessageInternal_beforeAddMessage(MessageType.Parameters params, SignedMessage message, Text decorated, GameProfile sender, boolean onlyShowSecureChat, Instant receptionTimestamp, CallbackInfoReturnable info) { + this.sender = sender; + } + + @Inject(method = "processChatMessageInternal", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/ChatHud;addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)V", shift = At.Shift.AFTER)) + private void onProcessChatMessageInternal_afterAddMessage(MessageType.Parameters params, SignedMessage message, Text decorated, GameProfile sender, boolean onlyShowSecureChat, Instant receptionTimestamp, CallbackInfoReturnable info) { + this.sender = null; + } + + @Override + public GameProfile smegma$getSender() { + return sender; + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/mixin/MinecraftClientMixin.java b/src/main/java/me/retucio/camtweaks/mixin/MinecraftClientMixin.java new file mode 100644 index 0000000..10887eb --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/MinecraftClientMixin.java @@ -0,0 +1,17 @@ +package me.retucio.camtweaks.mixin; + +import me.retucio.camtweaks.CameraTweaks; +import net.minecraft.client.MinecraftClient; +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(MinecraftClient.class) +public abstract class MinecraftClientMixin { + + @Inject(method = "tick", at = @At("HEAD"), cancellable = true) + private void onTick(CallbackInfo ci) { + CameraTweaks.INSTANCE.onTick(); + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/MouseMixin.java b/src/main/java/me/retucio/camtweaks/mixin/MouseMixin.java new file mode 100644 index 0000000..7495d35 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/MouseMixin.java @@ -0,0 +1,28 @@ +package me.retucio.camtweaks.mixin; + +import me.retucio.camtweaks.CameraTweaks; +import me.retucio.camtweaks.event.events.MouseClickEvent; +import me.retucio.camtweaks.event.events.MouseScrollEvent; +import net.minecraft.client.Mouse; +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; + +import static me.retucio.camtweaks.CameraTweaks.EVENT_BUS; + +@Mixin(Mouse.class) +public abstract class MouseMixin { + + @Inject(method = "onMouseButton", at = @At("HEAD"), cancellable = true) + private void onMouseButton(long window, int button, int action, int mods, CallbackInfo ci) { + MouseClickEvent event = EVENT_BUS.post(new MouseClickEvent(action, button)); + if (event.isCancelled()) ci.cancel(); + } + + @Inject(method = "onMouseScroll", at = @At("HEAD"), cancellable = true) + private void onMouseScroll(long window, double horizontal, double vertical, CallbackInfo ci) { + MouseScrollEvent event = EVENT_BUS.post(new MouseScrollEvent(horizontal, vertical)); + if (event.isCancelled()) ci.cancel(); + } +} diff --git a/src/main/java/me/retucio/camtweaks/mixin/StringHelperMixin.java b/src/main/java/me/retucio/camtweaks/mixin/StringHelperMixin.java new file mode 100644 index 0000000..be51ad3 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/StringHelperMixin.java @@ -0,0 +1,18 @@ +package me.retucio.camtweaks.mixin; + +import me.retucio.camtweaks.module.modules.BetterChat; +import net.minecraft.util.StringHelper; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import static me.retucio.camtweaks.CameraTweaks.moduleManager; + +@Mixin(StringHelper.class) +public abstract class StringHelperMixin { + @ModifyArg(method = "truncateChat", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/StringHelper;truncate(Ljava/lang/String;IZ)Ljava/lang/String;"), index = 1) + private static int truncate(int maxLength) { + BetterChat betterChat = moduleManager.getModuleByClass(BetterChat.class); + return ((betterChat.isEnabled() && betterChat.noCharLimit.isEnabled()) ? Integer.MAX_VALUE : maxLength); + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/mixin/accessor/StatusEffectInstanceAccessor.java b/src/main/java/me/retucio/camtweaks/mixin/accessor/StatusEffectInstanceAccessor.java new file mode 100644 index 0000000..4882be7 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/mixin/accessor/StatusEffectInstanceAccessor.java @@ -0,0 +1,12 @@ +package me.retucio.camtweaks.mixin.accessor; + +import net.minecraft.entity.effect.StatusEffectInstance; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(StatusEffectInstance.class) +public interface StatusEffectInstanceAccessor { + + @Accessor("duration") + void setDuration(int duration); +} diff --git a/src/main/java/me/retucio/camtweaks/module/Module.java b/src/main/java/me/retucio/camtweaks/module/Module.java new file mode 100644 index 0000000..ccc5792 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/Module.java @@ -0,0 +1,139 @@ +package me.retucio.camtweaks.module; + +import me.retucio.camtweaks.CameraTweaks; +import me.retucio.camtweaks.event.Subscribe; +import me.retucio.camtweaks.module.settings.BooleanSetting; +import me.retucio.camtweaks.module.settings.EnumSetting; +import me.retucio.camtweaks.module.settings.KeySetting; +import me.retucio.camtweaks.module.settings.Setting; +import me.retucio.camtweaks.util.ChatUtil; +import net.minecraft.client.MinecraftClient; +import org.lwjgl.glfw.GLFW; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +// clase base para los módulos +public class Module { + + // atributos + private String name; + private String description; + private boolean enabled; + + // dejar al usuario elegir si el módulo debería apagarse tras soltar su tecla asignada, o si la tecla debería alternar su estado + protected EnumSetting keyMode = new EnumSetting<>("modo de tecla", "cómo interpretar la tecla configurada", KeyModes.class, KeyModes.TOGGLE); + protected KeySetting bind = new KeySetting("tecla", "tecla asignada al módulo, ESC para desactivar", GLFW.GLFW_KEY_UNKNOWN); + protected BooleanSetting notify = new BooleanSetting("notificar", "notificar en el chat al activar / desactivar", true); + + private final List settings = new ArrayList<>(); + + protected MinecraftClient mc = MinecraftClient.getInstance(); + + public Module(String name, String description) { + this.name = name; + this.description = description; + addSettings(bind, keyMode, notify); + } + + + // ajustes + public List getSettings() { + return settings; + } + + public void addSetting(Setting setting) { + settings.add(setting); + } + + public void addSettings(Setting... settings) { + for (Setting setting : settings) addSetting(setting); + } + + + // encendido y apagado del módulo + public void toggle() { + enabled = !enabled; + if (enabled) onEnable(); + else onDisable(); + } + + public void onEnable() { + if (notify.isEnabled()) ChatUtil.info(getName() + " fue activado"); + for (Method method : getClass().getDeclaredMethods()) { + if (method.isAnnotationPresent(Subscribe.class)) { + CameraTweaks.EVENT_BUS.register(this); + break; + } + } + } + + public void onDisable() { + if (notify.isEnabled()) ChatUtil.info(getName() + " fue desactivado"); + if (CameraTweaks.EVENT_BUS.isRegistered(this)) + CameraTweaks.EVENT_BUS.unregister(this); + } + + + // otros métodos + public void onTick() {} + public void onKey(int key, int action) {} + + public boolean nullCheck() { + return mc.player == null; + } + public boolean fullNullCheck() { + return mc.player == null || mc.world == null; + } + + // getters y setters + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + + public boolean isEnabled() { + return enabled; + } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (enabled) onEnable(); + else onDisable(); + } + + public boolean shouldToggleOnBindRelease() { + return keyMode.is(KeyModes.HOLD); + } + + public int getKey() { + return bind.getKey(); + } + + public void setKey(int key) { + bind.setKey(key); + bind.setDefaultKey(key); + } + + public KeySetting getBind() { + return bind; + } + + public enum KeyModes { + HOLD("mantener"), + TOGGLE("alternar"); + + private final String name; + KeyModes(String name) { this.name = name; } + @Override public String toString() { return name; } + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/module/ModuleManager.java b/src/main/java/me/retucio/camtweaks/module/ModuleManager.java new file mode 100644 index 0000000..598ee24 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/ModuleManager.java @@ -0,0 +1,67 @@ +package me.retucio.camtweaks.module; + +import me.retucio.camtweaks.event.EventBus; +import me.retucio.camtweaks.event.Subscribe; +import me.retucio.camtweaks.module.modules.*; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + + +// donde se regisran los módulos, y los "listeners" de eventos en cada módulo que lo necesite +public class ModuleManager { + + private final List modules = new ArrayList<>(); + private final EventBus eventBus; + + public ModuleManager(EventBus bus) { + this.eventBus = bus; + addModules(); + } + + private void addModules() { + modules.add(new Fullbright()); + modules.add(new Zoom()); + modules.add(new Perspective()); + modules.add(new HandView()); + modules.add(new BetterChat()); + + // registrar los "listeners" necesarios + for (Module module : getEnabledModules()) { + for (Method method : module.getClass().getDeclaredMethods()) { + if (method.isAnnotationPresent(Subscribe.class)) { + eventBus.register(module); + break; + } + } + } + } + + + // para obtener módulos más fácilmente (por nombre, clase, o la lista completa) + public List getModules() { + return modules; + } + + public List getEnabledModules() { + List enabledModules = new ArrayList<>(); + for (Module module : modules) + if (module.isEnabled()) enabledModules.add(module); + + return enabledModules; + } + + public Module getModuleByName(String name) { + for (Module module : modules) + if (module.getName().equalsIgnoreCase(name)) return module; + return null; + } + + public T getModuleByClass(Class clazz) { + for (Module module : modules) + if (clazz.isInstance(module)) + return clazz.cast(module); + return null; + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/module/modules/BetterChat.java b/src/main/java/me/retucio/camtweaks/module/modules/BetterChat.java new file mode 100644 index 0000000..0e212f4 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/modules/BetterChat.java @@ -0,0 +1,214 @@ +package me.retucio.camtweaks.module.modules; + +import com.mojang.authlib.GameProfile; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import me.retucio.camtweaks.CameraTweaks; +import me.retucio.camtweaks.command.CommandManager; +import me.retucio.camtweaks.event.Subscribe; +import me.retucio.camtweaks.event.events.ClientClickEvent; +import me.retucio.camtweaks.event.events.ReceiveMessageEvent; +import me.retucio.camtweaks.event.events.SendMessageEvent; +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.module.settings.BooleanSetting; +import me.retucio.camtweaks.module.settings.NumberSetting; +import me.retucio.camtweaks.util.ChatUtil; +import me.retucio.camtweaks.util.interfaces.IChatHudLine; +import me.retucio.camtweaks.util.interfaces.IChatHudLineVisible; +import me.retucio.camtweaks.util.interfaces.TextVisitor; +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.PlayerSkinDrawer; +import net.minecraft.client.gui.hud.ChatHudLine; +import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.text.*; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +// continúa en ChatHudMixin, ChatScreenMixin, InGameHudMixin & StringHelperMixin +public class BetterChat extends Module { + + // ajustes + public BooleanSetting timestamps = new BooleanSetting("sello de tiempo", "muestra a qué hora se ha enviado un mensaje", false); + public BooleanSetting timestampSecs = new BooleanSetting("mostrar segundos", "muestra segundos también en el sello de tiempo", false); + public BooleanSetting showHeads = new BooleanSetting("cabezas", "muestra la cabeza del jugador junto a su mensaje", true); + public BooleanSetting coordsProtection = new BooleanSetting("proteger coordenadas", "evitar enviar coordenadas por el chat", true); + + public BooleanSetting keepHistory = new BooleanSetting("no borrar chat", "no borrar el chat tras desconectarse", true); + public BooleanSetting logger = new BooleanSetting("registro", "evita que se borre el chat de un server", true); + public BooleanSetting noCharLimit = new BooleanSetting("quitar límite de caracteres", "te deja escribir mensajes tan largos como desees", false); + public NumberSetting chatHistoryExtraLength = new NumberSetting("expandir chat", "cuántas líneas añadir al historial del chat", + 0, 0, 1000, 1); + + public BetterChat() { + super("chat plus", "mejoras para el chat"); + addSettings(timestamps, timestampSecs, showHeads, coordsProtection, keepHistory, logger, noCharLimit, chatHistoryExtraLength); + + updateDateFormat(); + timestamps.onUpdate(b -> timestampSecs.setVisible(b)); + timestampSecs.onUpdate(x -> updateDateFormat()); + } + + public final IntList lines = new IntArrayList(); + private static final Pattern antiClearRegex = Pattern.compile("\\n(\\n|\\s)+\\n"); + private static final Pattern usernameRegex = Pattern.compile("^(?:<[0-9]{2}:[0-9]{2}>\\s)?<(.*?)>.*"); + + private record CustomHeadEntry(String prefix, Identifier texture) {} + private static final List CUSTOM_HEAD_ENTRIES = new ArrayList<>(); + private static final Pattern TIMESTAMP_REGEX = Pattern.compile("^<\\d{1,2}:\\d{1,2}>"); + + private SimpleDateFormat dateFormat; + private void updateDateFormat() { + dateFormat = new SimpleDateFormat(timestampSecs.isEnabled() ? "HH:mm:ss" : "HH:mm"); + } + + private static final Pattern coordRegex = Pattern.compile("(?-?\\d{3,}(?:\\.\\d*)?)(?:\\s+(?-?\\d{1,3}(?:\\.\\d*)?))?\\s+(?-?\\d{3,}(?:\\.\\d*)?)"); + private boolean containsCoordinates(String message) { + return coordRegex.matcher(message).find(); + } + + @Subscribe + public void onReceiveMessage(ReceiveMessageEvent event) { + Text message = event.getMessage(); + + // registrar mensajes para evitar su eliminación + if (logger.isEnabled()) { + String messageString = message.getString(); + if (antiClearRegex.matcher(messageString).find()) { + MutableText newMessage = Text.empty(); + TextVisitor.visit(message, (text, style, string) -> { + Matcher antiClearMatcher = antiClearRegex.matcher(string); + if (antiClearMatcher.find()) + newMessage.append(Text.literal(antiClearMatcher.replaceAll("\n\n")).setStyle(style)); + else + newMessage.append(text.copyContentOnly().setStyle(style)); + + return Optional.empty(); + }, Style.EMPTY); + message = newMessage; + } + } + + // agregar sellos de tiempo a los mensajes + if (timestamps.isEnabled()) { + Text timestamp = Text.literal("<" + dateFormat.format(new Date()) + "> ").formatted(Formatting.GRAY); + message = Text.empty().append(timestamp).append(message); + } + + // modificar el mensaje final + event.setMessage(message); + } + + @Subscribe + private void onSendMessage(SendMessageEvent event) { + String message = event.getMessage(); + + // evitar mandar coordenadas por el chat + if (coordsProtection.isEnabled() && containsCoordinates(message)) { + ChatUtil.warn(Text.literal("cuidadito con las coordenadas chavalín").append( + getSendButton(message))); + + event.cancel(); + return; + } + event.setMessage(message); + } + + static { + CUSTOM_HEAD_ENTRIES.add(new CustomHeadEntry("smegma", Identifier.of(CameraTweaks.MOD_ID, "icon_chat.png"))); + } + + public void beforeDrawMessage(DrawContext context, ChatHudLine.Visible line, int y, int color) { + if (!isEnabled() || !showHeads.isEnabled()) return; + + // dibujar la cabeza al principio del mensaje + if (((IChatHudLineVisible) (Object) line).smegma$isStartOfEntry()) + drawTexture(context, (IChatHudLine) (Object) line, y, color); + + context.getMatrices().pushMatrix(); + context.getMatrices().translate(10, 0); + } + + public void afterDrawMessage(DrawContext context) { + // finalizar renderizado de la cabeza + if (!isEnabled() || !showHeads.isEnabled()) return; + context.getMatrices().popMatrix(); + } + + private void drawTexture(DrawContext context, IChatHudLine line, int y, int color) { + String text = line.smegma$getText().trim(); + + int startOffset = 0; + + // buscar el sello del tiempo si lo hay para dibujar la cabeza correctamente + try { + Matcher m = TIMESTAMP_REGEX.matcher(text); + if (m.find()) startOffset = m.end() + 1; + } catch (IllegalStateException ignored) {} + + // dibujar el icono del mod + for (CustomHeadEntry entry : CUSTOM_HEAD_ENTRIES) { + if (text.startsWith(entry.prefix(), startOffset)) { + context.drawTexture(RenderPipelines.GUI_TEXTURED, entry.texture(), 0, y, 0, 0, 8, 8, 64, 64, 64, 64, color); + return; + } + } + + // obtener y dibujar la cabeza del jugador + GameProfile sender = getSender(line, text); + if (sender == null) return; + + PlayerListEntry entry = mc.getNetworkHandler().getPlayerListEntry(sender.getId()); + if (entry == null) return; + + PlayerSkinDrawer.draw(context, entry.getSkinTextures(), 0, y, 8); + } + + // obtener el jugador que envió un mensaje + private GameProfile getSender(IChatHudLine line, String text) { + GameProfile sender = line.smegma$getSender(); + + if (sender == null) { + Matcher usernameMatcher = usernameRegex.matcher(text); + + if (usernameMatcher.matches()) { + String username = usernameMatcher.group(1); + + PlayerListEntry entry = mc.getNetworkHandler().getPlayerListEntry(username); + if (entry != null) sender = entry.getProfile(); + } + } + return sender; + } + + // botón para enviar mensaje con coordenadas de todos modos + private MutableText getSendButton(String message) { + MutableText sendButton = Text.literal("[ME LA SUDA]"); + MutableText hintBaseText = Text.literal(""); + + MutableText hintMsg = Text.literal("enviar de todos modos:"); + hintMsg.setStyle(hintBaseText.getStyle().withFormatting(Formatting.GRAY)); + hintBaseText.append(hintMsg); + + hintBaseText.append(Text.literal("\n" + message)); + + sendButton.setStyle(sendButton.getStyle() + .withFormatting(Formatting.DARK_RED) + .withClickEvent(new ClientClickEvent(CommandManager.getCommandByName("send").toString(message))) + .withHoverEvent(new HoverEvent.ShowText(hintBaseText))); + + return sendButton; + } + + public void removeLine(int index) { + lines.removeInt(index); + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/module/modules/Fullbright.java b/src/main/java/me/retucio/camtweaks/module/modules/Fullbright.java new file mode 100644 index 0000000..02122fa --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/modules/Fullbright.java @@ -0,0 +1,58 @@ +package me.retucio.camtweaks.module.modules; + +import me.retucio.camtweaks.mixin.accessor.StatusEffectInstanceAccessor; +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.module.settings.EnumSetting; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.registry.Registries; +import org.lwjgl.glfw.GLFW; + +// continúa en LightmapTextureManagerMixin +public class Fullbright extends Module { + + public EnumSetting mode = new EnumSetting<>("modo", "qué modo de iluminación emplear (usar poción con shaders)", Modes.class, Modes.GAMMA); + + public Fullbright() { + super("brilli-brilli", "deshabilita la oscuridad"); + setKey(GLFW.GLFW_KEY_B); + addSetting(mode); + + mode.onUpdate(mode -> { if (mode != Modes.POTION) disableNightVision(); }); + } + + @Override + public void onDisable() { + disableNightVision(); + } + + @Override + public void onTick() { + if (mc.player == null || !mode.getValue().equals(Modes.POTION)) return; + + if (mc.player.hasStatusEffect(Registries.STATUS_EFFECT.getEntry(StatusEffects.NIGHT_VISION.value()))) { + StatusEffectInstance instance = mc.player.getStatusEffect(Registries.STATUS_EFFECT.getEntry(StatusEffects.NIGHT_VISION.value())); + if (instance != null && instance.getDuration() < 69) ((StatusEffectInstanceAccessor) instance).setDuration(69); + } else { + mc.player.addStatusEffect(new StatusEffectInstance(Registries.STATUS_EFFECT.getEntry(StatusEffects.NIGHT_VISION.value()), 69, 0)); + } + } + + private void disableNightVision() { + if (mc.player == null) return; + if (mc.player.hasStatusEffect(Registries.STATUS_EFFECT.getEntry(StatusEffects.NIGHT_VISION.value()))) + mc.player.removeStatusEffect(Registries.STATUS_EFFECT.getEntry(StatusEffects.NIGHT_VISION.value())); + } + + + public enum Modes { + GAMMA("gamma"), + POTION("poción"); + + private final String name; + Modes(String name) { this.name = name; } + + @Override + public String toString() { return name; } + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/module/modules/GUI.java b/src/main/java/me/retucio/camtweaks/module/modules/GUI.java new file mode 100644 index 0000000..97263e3 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/modules/GUI.java @@ -0,0 +1,49 @@ +package me.retucio.camtweaks.module.modules; + +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.module.settings.NumberSetting; +import me.retucio.camtweaks.module.settings.StringSetting; +import me.retucio.camtweaks.util.Colors; +import org.lwjgl.glfw.GLFW; + +import java.awt.*; + +import static me.retucio.camtweaks.CameraTweaks.commandManager; +import static me.retucio.camtweaks.util.Colors.*; + +public class GUI extends Module { + + public NumberSetting red = new NumberSetting("rojo", "cantidad de rojo en el RGB del marco", 0, 0, 255, 1); + public NumberSetting green = new NumberSetting("verde", "cantidad de verde en el RGB del marco", 0, 0, 255, 1); + public NumberSetting blue = new NumberSetting("azul", "cantidad de azul en el RGB del marco", 255, 0, 255, 1); + public NumberSetting alpha = new NumberSetting("alpha", "opacidad del marco", 180, 0, 255, 1); + + public NumberSetting scrollSens = new NumberSetting("sensibilidad del scroll", "qué tan sensible es la interfaz a la rueda del ratón", + 5, 0, 15, 0.5); + + public StringSetting commandPrefix = new StringSetting("prefijo", "prefijo de los comandos", commandManager.getPrefix(), 10); + + public GUI() { + super("interfaz", "ajustes de la interfaz, y otros misceláneos"); + addSettings(red, green, blue, alpha, scrollSens, commandPrefix); + setKey(GLFW.GLFW_KEY_RIGHT_SHIFT); + + keyMode.setVisible(false); + notify.setVisible(false); + + commandPrefix.onUpdate(prefix -> commandManager.setPrefix(prefix)); + red.onUpdate(r -> {frameHeadRed = r.intValue(); updateFrameHeadColor();}); + green.onUpdate(g -> {frameHeadGreen = g.intValue(); updateFrameHeadColor();}); + blue.onUpdate(b -> {frameHeadBlue = b.intValue(); updateFrameHeadColor();}); + alpha.onUpdate(a -> {frameHeadAlpha = a.intValue(); updateFrameHeadColor();}); + } + + private void updateFrameHeadColor() { + Colors.frameHeadColor = new Color( + frameHeadRed, + frameHeadGreen, + frameHeadBlue, + frameHeadAlpha + ).getRGB(); + } +} diff --git a/src/main/java/me/retucio/camtweaks/module/modules/HandView.java b/src/main/java/me/retucio/camtweaks/module/modules/HandView.java new file mode 100644 index 0000000..f8ddd66 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/modules/HandView.java @@ -0,0 +1,140 @@ +package me.retucio.camtweaks.module.modules; + +import me.retucio.camtweaks.event.Subscribe; +import me.retucio.camtweaks.event.events.HeldItemRendererEvent; +import me.retucio.camtweaks.event.events.RenderArmEvent; +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.module.settings.BooleanSetting; +import me.retucio.camtweaks.module.settings.NumberSetting; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.Hand; +import net.minecraft.util.math.RotationAxis; +import org.joml.Vector3d; +import org.lwjgl.glfw.GLFW; + +public class HandView extends Module { + + // ajustes de animaciones + public BooleanSetting oldAnimations = new BooleanSetting("animaciones viejas", "como las de la 1.8, al pegar", false); + public BooleanSetting skipSwapping = new BooleanSetting("no swap", "se salta la animación de cambiar items de mano", false); + public BooleanSetting noFood = new BooleanSetting("no comer", "no renderiza la animación de comer", false); + + // ajustes de mano principal + public NumberSetting scaleMainX = new NumberSetting("escala de la mano principal (X)", ".", + 1, 0.01, 5, 0.1); + public NumberSetting scaleMainY = new NumberSetting("escala de la mano principal (Y)", ".", + 1, 0.01, 5, 0.1); + public NumberSetting scaleMainZ = new NumberSetting("escala de la mano principal (Z)", ".", + 1, 0.01, 5, 0.1); + + public NumberSetting posMainX = new NumberSetting("posición de la mano principal (X)", ".", + 0, -3, 3, 0.1); + public NumberSetting posMainY = new NumberSetting("posición de la mano principal (Y)", ".", + 0, -3, 3, 0.1); + public NumberSetting posMainZ = new NumberSetting("posición de la mano principal (Z)", ".", + 0, -3, 3, 0.1); + + public NumberSetting rotMainX = new NumberSetting("rotación de la mano principal (X)", ".", + 0, -180, 180, 1); + public NumberSetting rotMainY = new NumberSetting("rotación de la mano principal (Y)", ".", + 0, -180, 180, 1); + public NumberSetting rotMainZ = new NumberSetting("rotación de la mano principal (Z)", ".", + 0, -180, 180, 1); + + // ajustes de mano secundaria + public NumberSetting scaleOffX = new NumberSetting("escala de la mano secundaria (X)", ".", + 1, 0.01, 5, 0.1); + public NumberSetting scaleOffY = new NumberSetting("escala de la mano secundaria (Y)", ".", + 1, 0.01, 5, 0.1); + public NumberSetting scaleOffZ = new NumberSetting("escala de la mano secundaria (Z)", ".", + 1, 0.01, 5, 0.1); + + public NumberSetting posOffX = new NumberSetting("posición de la mano secundaria (X)", ".", + 0, -3, 3, 0.1); + public NumberSetting posOffY = new NumberSetting("posición de la mano secundaria (Y)", ".", + 0, -3, 3, 0.1); + public NumberSetting posOffZ = new NumberSetting("posición de la mano secundaria (Z)", ".", + 0, -3, 3, 0.1); + + public NumberSetting rotOffX = new NumberSetting("rotación de la mano secundaria (X)", ".", + 0, -180, 180, 1); + public NumberSetting rotOffY = new NumberSetting("rotación de la mano secundaria (Y)", ".", + 0, -180, 180, 1); + public NumberSetting rotOffZ = new NumberSetting("rotación de la mano secundaria (Z)", ".", + 0, -180, 180, 1); + + // brazo + public NumberSetting scaleArmX = new NumberSetting("escala del brazo (X)", ".", + 1, 0.01, 5, 0.1); + public NumberSetting scaleArmY = new NumberSetting("escala del brazo (Y)", ".", + 1, 0.01, 5, 0.1); + public NumberSetting scaleArmZ = new NumberSetting("escala del brazo (Z)", ".", + 1, 0.01, 5, 0.1); + + public NumberSetting posArmX = new NumberSetting("posición del brazo (X)", ".", + 0, -3, 3, 0.1); + public NumberSetting posArmY = new NumberSetting("posición del brazo (Y)", ".", + 0, -3, 3, 0.1); + public NumberSetting posArmZ = new NumberSetting("posición del brazo (Z)", ".", + 0, -3, 3, 0.1); + + public NumberSetting rotArmX = new NumberSetting("rotación del brazo (X)", ".", + 0, -180, 180, 1); + public NumberSetting rotArmY = new NumberSetting("rotación del brazo (Y)", ".", + 0, -180, 180, 1); + public NumberSetting rotArmZ = new NumberSetting("rotación del brazo (Z)", ".", + 0, -180, 180, 1); + + public HandView() { + super("manos", "modifica la manera en la que se renderizan las manos"); + addSettings( + oldAnimations, skipSwapping, noFood, + scaleMainX, scaleMainY, scaleMainZ, + posMainX, posMainY, posMainZ, + rotMainX, rotMainY, rotMainZ, + scaleOffX, scaleOffY, scaleOffZ, + posOffX, posOffY, posOffZ, + rotOffX, rotOffY, rotOffZ, + scaleArmX, scaleArmY, scaleArmZ, + posArmX, posArmY, posArmZ, + rotArmX, rotArmY, rotArmZ); + setKey(GLFW.GLFW_KEY_UNKNOWN); + } + + @Subscribe + public void onHeldItemRender(HeldItemRendererEvent event) { + if (event.getHand() == Hand.MAIN_HAND) { + scale(event.getMatrices(), new Vector3d(scaleMainX.getValue(), scaleMainY.getValue(), scaleMainZ.getValue())); + translate(event.getMatrices(), new Vector3d(posMainX.getValue(), posMainY.getValue(), posMainZ.getValue())); + rotate(event.getMatrices(), new Vector3d(rotMainX.getValue(), rotMainY.getValue(), rotMainZ.getValue())); + } + else { + scale(event.getMatrices(), new Vector3d(scaleOffX.getValue(), scaleOffY.getValue(), scaleOffZ.getValue())); + translate(event.getMatrices(), new Vector3d(posOffX.getValue(), posOffY.getValue(), posOffZ.getValue())); + rotate(event.getMatrices(), new Vector3d(rotOffX.getValue(), rotOffY.getValue(), rotOffZ.getValue())); + } + } + + @Subscribe + public void onRenderArm(RenderArmEvent event) { + scale(event.getMatrices(), new Vector3d(scaleArmX.getValue(), scaleArmY.getValue(), scaleArmZ.getValue())); + translate(event.getMatrices(), new Vector3d(posArmX.getValue(), posArmY.getValue(), posArmZ.getValue())); + rotate(event.getMatrices(), new Vector3d(rotArmX.getValue(), rotArmY.getValue(), rotArmZ.getValue())); + } + + private void rotate(MatrixStack matrix, Vector3d rotation) { + matrix.multiply(RotationAxis.POSITIVE_X.rotationDegrees((float) rotation.x)); + matrix.multiply(RotationAxis.POSITIVE_Y.rotationDegrees((float) rotation.y)); + matrix.multiply(RotationAxis.POSITIVE_Z.rotationDegrees((float) rotation.z)); + } + + private void scale(MatrixStack matrix, Vector3d scale) { + matrix.scale((float) scale.x, (float) scale.y, (float) scale.z); + } + + private void translate(MatrixStack matrix, Vector3d translation) { + matrix.translate((float) translation.x, (float) translation.y, (float) translation.z); + } + + +} diff --git a/src/main/java/me/retucio/camtweaks/module/modules/Perspective.java b/src/main/java/me/retucio/camtweaks/module/modules/Perspective.java new file mode 100644 index 0000000..2de9ea7 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/modules/Perspective.java @@ -0,0 +1,61 @@ +package me.retucio.camtweaks.module.modules; + +import me.retucio.camtweaks.event.Subscribe; +import me.retucio.camtweaks.event.events.MouseScrollEvent; +import me.retucio.camtweaks.event.events.PerspectiveChangeEvent; +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.module.settings.*; +import me.retucio.camtweaks.util.KeyUtil; +import org.lwjgl.glfw.GLFW; + +// continúa en CameraMixin +public class Perspective extends Module { + + private double distance; + + // ajustes + public BooleanSetting clip = new BooleanSetting( + "clip", "atravesar bloques con la cámara", true); + + public NumberSetting defaultDistance = new NumberSetting( + "distancia", "distancia a la que está la cámara del jugador por defecto", + 4, 1, 20, 0.2); + + public NumberSetting scrollSens = new NumberSetting( + "sensibilidad del scroll", "sensibilidad de la rueda del ratón (0 para desactivar)", + 1, 0, 8, 0.1); + + public KeySetting scrollKey = new KeySetting("tecla del scroll", "qué tecla mantener para usar la rueda del ratón", GLFW.GLFW_KEY_LEFT_ALT); + + public Perspective() { + super("perspectiva", "añade cositas chulas al cambio de perspectiva"); + addSettings(clip, defaultDistance, scrollSens, scrollKey); + setKey(GLFW.GLFW_KEY_UNKNOWN); + } + + @Override + public void onEnable() { + distance = defaultDistance.getValue(); + super.onEnable(); + } + + @Subscribe + public void onPerspectiveChange(PerspectiveChangeEvent event) { + distance = defaultDistance.getValue(); + } + + @Subscribe + public void onMouseScroll(MouseScrollEvent event) { + if (mc.options.getPerspective() == net.minecraft.client.option.Perspective.FIRST_PERSON + || mc.currentScreen != null || scrollSens.getValue() <= 0) return; + + if (KeyUtil.isKeyDown(scrollKey.getKey())) { + distance -= event.getVertical() / 4 * (scrollSens.getValue() * distance); + event.cancel(); + } + } + + public double getDistance() { + return distance; + } +} diff --git a/src/main/java/me/retucio/camtweaks/module/modules/Zoom.java b/src/main/java/me/retucio/camtweaks/module/modules/Zoom.java new file mode 100644 index 0000000..4c7e999 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/modules/Zoom.java @@ -0,0 +1,99 @@ +package me.retucio.camtweaks.module.modules; + +import me.retucio.camtweaks.CameraTweaks; +import me.retucio.camtweaks.event.Subscribe; +import me.retucio.camtweaks.event.events.GetFOVEvent; +import me.retucio.camtweaks.event.events.MouseScrollEvent; +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.module.settings.BooleanSetting; +import me.retucio.camtweaks.module.settings.KeySetting; +import me.retucio.camtweaks.module.settings.NumberSetting; +import me.retucio.camtweaks.util.ChatUtil; +import me.retucio.camtweaks.util.KeyUtil; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; + +// continúa en GameRendererMixin +public class Zoom extends Module { + + public BooleanSetting showHands = new BooleanSetting("mostrar manos", "esconde o muestra las manos al hacer zoom", true); + public BooleanSetting showHUD = new BooleanSetting("mostrar HUD", "esconde o muestra los indicadores en pantalla", true); + + public NumberSetting scrollSens = new NumberSetting("sensibilidad del scroll", "sensibilidad de la rueda del ratón (0 para desactivar)", + 0.4, 0, 8, 0.1); + + public KeySetting scrollKey = new KeySetting("tecla del scroll", "qué tecla mantener para usar la rueda del ratón", GLFW.GLFW_KEY_LEFT_CONTROL); + + public NumberSetting defaultZoom = new NumberSetting("zoom", "cantidad de zoom", + 6, 1, 10, 0.1); + + public NumberSetting mouseSensMultiplier = new NumberSetting("sensibilidad", "multiplicador de la sensibilidad del ratón", + 0.7, 0.01, 1, 0.05); + + public BooleanSetting smoothCam = new BooleanSetting("cámara cinemática", "usa la cámara cinemática mientras hagas zoom", false); + + private boolean prevSmoothCam; + private double prevMouseSens; + private double prevFov; + private boolean prevHUD; + private double value; + + public Zoom() { + super("zoom", "lupa"); + super.keyMode.setValue(KeyModes.HOLD); + super.keyMode.setDefaultValue(KeyModes.HOLD); + super.notify.setEnabled(false); + super.notify.setDefaultValue(false); + setKey(GLFW.GLFW_KEY_F); + addSettings(showHands, showHUD, scrollSens, scrollKey, defaultZoom, mouseSensMultiplier, smoothCam); + } + + @Override + public void onEnable() { + CameraTweaks.EVENT_BUS.register(this); + + prevSmoothCam = mc.options.smoothCameraEnabled; + prevMouseSens = mc.options.getMouseSensitivity().getValue(); + prevFov = mc.options.getFov().getValue(); + prevHUD = mc.options.hudHidden; + mc.options.hudHidden = !showHUD.isEnabled(); + + value = defaultZoom.getValue(); + } + + @Override + public void onDisable() { + CameraTweaks.EVENT_BUS.unregister(this); + + mc.options.smoothCameraEnabled = prevSmoothCam; + mc.options.getMouseSensitivity().setValue(prevMouseSens); + mc.options.hudHidden = prevHUD; + + mc.worldRenderer.scheduleTerrainUpdate(); + } + + @Override + public void onTick() { + mc.options.smoothCameraEnabled = smoothCam.isEnabled(); + if (!smoothCam.isEnabled()) + mc.options.getMouseSensitivity().setValue(prevMouseSens * mouseSensMultiplier.getValue()); + } + + @Subscribe + private void onMouseScroll(MouseScrollEvent event) { + if (scrollSens.getValue() > 0 && isEnabled() && KeyUtil.isKeyDown(scrollKey.getKey())) { + value += event.getVertical() * 0.25 * (scrollSens.getValue() * value); + if (value < 1) value = 1; + + event.cancel(); + } + } + + @Subscribe + private void onGetFov(GetFOVEvent event) { + event.setFov((float) (event.getFov() / value)); + + if (prevFov != event.getFov()) mc.worldRenderer.scheduleTerrainUpdate(); + prevFov = event.getFov(); + } +} diff --git a/src/main/java/me/retucio/camtweaks/module/settings/BooleanSetting.java b/src/main/java/me/retucio/camtweaks/module/settings/BooleanSetting.java new file mode 100644 index 0000000..117da5b --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/settings/BooleanSetting.java @@ -0,0 +1,50 @@ +package me.retucio.camtweaks.module.settings; + +import java.util.function.Consumer; + +// ajuste booleano, es decir, o se encuentra encencido o apagado (similar a un interruptor) +public class BooleanSetting extends Setting { + + private boolean defaultValue; + private boolean enabled; + + private Consumer updateListener; + + public BooleanSetting(String name, String description, boolean defaultValue) { + super(name, description); + this.enabled = defaultValue; + this.defaultValue = defaultValue; + } + + public void toggle() { + enabled = !enabled; + if (updateListener != null) updateListener.accept(enabled); + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + if (this.enabled != enabled) { + this.enabled = enabled; + if (updateListener != null) updateListener.accept(enabled); + } + } + + public boolean getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(boolean value) { + defaultValue = value; + } + + public void reset() { + setEnabled(defaultValue); + } + + public void onUpdate(Consumer listener) { + this.updateListener = listener; + } +} diff --git a/src/main/java/me/retucio/camtweaks/module/settings/EnumSetting.java b/src/main/java/me/retucio/camtweaks/module/settings/EnumSetting.java new file mode 100644 index 0000000..cd4ba9b --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/settings/EnumSetting.java @@ -0,0 +1,87 @@ +package me.retucio.camtweaks.module.settings; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +// ajuste que te deja elegir entre distintas opciones definidas, dentro de un Enum +public class EnumSetting> extends Setting { + + private final Class enumClass; + private final List values; + private E defaultValue; + + private E value; + private int index; + + private Consumer updateListener; + + public EnumSetting(String name, String description, Class enumClass, E defaultValue) { + super(name, description); + this.enumClass = enumClass; + this.values = Arrays.asList(enumClass.getEnumConstants()); + this.defaultValue = defaultValue; + this.value = defaultValue; + this.index = values.indexOf(defaultValue); + } + + public E getValue() { + return value; + } + + public void setValue(E value) { + if (this.value != value) { + this.value = value; + this.index = values.indexOf(value); + if (updateListener != null) updateListener.accept(value); + } + } + + public List getValues() { + return values; + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + if (this.index != index) + setValue(values.get(index)); + } + + public boolean is(E value) { + return this.value == value; + } + + // ciclar entre los valores del enum + public void cycle() { + if (index < values.size() - 1) + setIndex(index + 1); + else + setIndex(0); + } + + public void cycleBackwards() { + if (index > 0) + setIndex(index - 1); + else + setIndex(values.size() - 1); + } + + public void setDefaultValue(E value) { + defaultValue = value; + } + + public E getDefaultValue() { + return defaultValue; + } + + public void reset() { + setValue(defaultValue); + } + + public void onUpdate(Consumer listener) { + this.updateListener = listener; + } +} diff --git a/src/main/java/me/retucio/camtweaks/module/settings/KeySetting.java b/src/main/java/me/retucio/camtweaks/module/settings/KeySetting.java new file mode 100644 index 0000000..8d84ddb --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/settings/KeySetting.java @@ -0,0 +1,49 @@ +package me.retucio.camtweaks.module.settings; + +import me.retucio.camtweaks.util.KeyUtil; + +import java.util.function.Consumer; + +public class KeySetting extends Setting { + + private int key; + private int defaultKey; + + private Consumer updateListener; + + public KeySetting(String name, String description, int defaultKey) { + super(name, description); + this.key = defaultKey; + this.defaultKey = defaultKey; + } + + public int getKey() { + return key; + } + + public void setKey(int key) { + if (this.key == key) return; + this.key = key; + if (updateListener != null) updateListener.accept(key); + } + + public int getDefaultKey() { + return defaultKey; + } + + public void setDefaultKey(int key) { + this.defaultKey = key; + } + + public String getKeyName() { + return KeyUtil.getKeyName(key); + } + + public void reset() { + setKey(defaultKey); + } + + public void onUpdate(Consumer listener) { + this.updateListener = listener; + } +} diff --git a/src/main/java/me/retucio/camtweaks/module/settings/NumberSetting.java b/src/main/java/me/retucio/camtweaks/module/settings/NumberSetting.java new file mode 100644 index 0000000..88f6ac3 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/settings/NumberSetting.java @@ -0,0 +1,82 @@ +package me.retucio.camtweaks.module.settings; + +import java.util.function.Consumer; + +// ajuste numérico, es decir, que eliges un valor númerico entre el mínimo y el máximo disponibles +public class NumberSetting extends Setting { + + private double value; + private final double defaultValue; + + private final double min; + private final double max; + private final double increment; // el "increment" es el salto que hay entre valores disponibles + + private Consumer updateListener; + + public NumberSetting(String name, String description, double defaultValue, double min, double max, double increment) { + super(name, description); + this.defaultValue = defaultValue; + this.value = defaultValue; + this.min = min; + this.max = max; + this.increment = increment; + } + + // no permitir valores ajenos a los límites definidos + public static double clamp(double value, double min, double max) { + return Math.clamp(value, min, max); + } + + // como decía, el valor crece o decrece por el "increment" definido + public void increment(boolean positive) { + if (positive) value += increment; + else value -= increment; + value = clamp(value, min, max); + if (updateListener != null) updateListener.accept(value); + } + + + public double getValue() { + return value; + } + + public float getFloatValue() { + return (float) value; // en "float" por conveniencia + } + + public int getIntValue() { + return (int) value; // en "int" (número entero) por conveniencia + } + + public void setValue(double value) { + if (this.value == value) return; + double clamped = clamp(value, min, max); + this.value = Math.round(clamped / increment) * increment; + if (updateListener != null) updateListener.accept(this.value); + } + + public double getDefaultValue() { + return defaultValue; + } + + public void reset() { + setValue(defaultValue); + } + + public double getMin() { + return min; + } + + public double getMax() { + return max; + } + + public double getIncrement() { + return increment; + } + + public void onUpdate(Consumer listener) { + this.updateListener = listener; + } +} diff --git a/src/main/java/me/retucio/camtweaks/module/settings/Setting.java b/src/main/java/me/retucio/camtweaks/module/settings/Setting.java new file mode 100644 index 0000000..33ab92d --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/settings/Setting.java @@ -0,0 +1,30 @@ +package me.retucio.camtweaks.module.settings; + +// base para los tipos de ajustes +public class Setting { + + private final String name; + private final String description; + private boolean visible = true; + + protected Setting(String name, String description) { + this.name = name; + this.description = description; + } + + public boolean isVisible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } +} diff --git a/src/main/java/me/retucio/camtweaks/module/settings/StringSetting.java b/src/main/java/me/retucio/camtweaks/module/settings/StringSetting.java new file mode 100644 index 0000000..f1b2032 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/module/settings/StringSetting.java @@ -0,0 +1,50 @@ +package me.retucio.camtweaks.module.settings; + +import java.util.Objects; +import java.util.function.Consumer; + +public class StringSetting extends Setting { + + private String value; + private String defaultValue; + private final int maxLength; + + private Consumer updateListener; + + public StringSetting(String name, String description, String defaultValue, int maxLength) { + super(name, description); + this.value = defaultValue; + this.defaultValue = defaultValue; + this.maxLength = maxLength; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + if (this.value.equals(value)) return; + this.value = value.length() > maxLength ? value.substring(0, maxLength) : value; + if (updateListener != null) updateListener.accept(this.value); + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public void reset() { + setValue(defaultValue); + } + + public int getMaxLength() { + return maxLength; + } + + public void onUpdate(Consumer listener) { + this.updateListener = listener; + } +} diff --git a/src/main/java/me/retucio/camtweaks/ui/ClickGUI.java b/src/main/java/me/retucio/camtweaks/ui/ClickGUI.java new file mode 100644 index 0000000..d053ac2 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/ClickGUI.java @@ -0,0 +1,111 @@ +package me.retucio.camtweaks.ui; + +import me.retucio.camtweaks.CameraTweaks; +import me.retucio.camtweaks.event.Subscribe; +import me.retucio.camtweaks.event.events.MouseScrollEvent; +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.ui.settings.ClickGUISettingsFrame; +import me.retucio.camtweaks.ui.settings.SettingsFrame; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; + +import java.util.ArrayList; +import java.util.List; + +import static me.retucio.camtweaks.ui.settings.ClickGUISettingsFrame.guiSettings; + +// interfaz gráfica, se abre con el shift derecho por defecto. aquí se encuentran los módulos y sus ajustes +public class ClickGUI extends Screen { + + public static final ClickGUI INSTANCE = new ClickGUI(); + + private final ModuleFrame modulesFrame = new ModuleFrame(20, 30, 100, 20); + private final List settingsFrames = new ArrayList<>(); + private final ClickGUISettingsFrame guiSettingsFrame = new ClickGUISettingsFrame(200, 30, 100, 20); + + protected ClickGUI() { + super(Text.of("interfaz")); + settingsFrames.add(guiSettingsFrame); + CameraTweaks.EVENT_BUS.register(this); + } + + @Override + public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + // renderizar el marco de los módulos + modulesFrame.render(ctx, mouseX, mouseY, delta); + modulesFrame.updatePosition(mouseX, mouseY); + + // renderizar el marco de los ajustes de cada módulo que lo tenga abierto. se abre haciendo clic derecho sobre el módulo + for (SettingsFrame sf : new ArrayList<>(settingsFrames)) { + sf.render(ctx, mouseX, mouseY, delta); + sf.updatePosition(mouseX, mouseY); + } + + super.render(ctx, mouseX, mouseY, delta); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + // detectar clics sobre los marcos + modulesFrame.mouseClicked(mouseX, mouseY, button); + for (SettingsFrame sf : new ArrayList<>(settingsFrames)) + sf.mouseClicked(mouseX, mouseY, button); + return super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public boolean mouseReleased(double mouseX, double mouseY, int button) { + // registrar cuándo se suelta el clic, en cada marco respectivamente + modulesFrame.mouseReleased(mouseX, mouseY, button); + for (SettingsFrame sf : new ArrayList<>(settingsFrames)) + sf.mouseRelease(mouseX, mouseY, button); + return super.mouseReleased(mouseX, mouseY, button); + } + + @Subscribe + public void onMouseScroll(MouseScrollEvent event) { + // subir y bajar la pantalla con la rueda del ratón + if (CameraTweaks.mc.currentScreen != this) return; + + modulesFrame.y += (int) (event.getVertical() * guiSettings.scrollSens.getValue()); + for (SettingsFrame sf : settingsFrames) + sf.y += (int) (event.getVertical() * guiSettings.scrollSens.getValue()); + } + + public void openSettingsFrame(Module module, int x, int y) { + // abrir un marco donde se encuentran los ajustes del módulo deseado + if (isSettingsFrameOpen(module)) return; // permitir un solo marco de ajustes por módulo + settingsFrames.add(new SettingsFrame(module, x + 120, y, 120, 18)); + } + + // cerrar el marco de ajustes + public void closeSettingsFrame(Module module) { + settingsFrames.removeIf(sf -> sf.module == module); + } + + // verificar si un módulo tiene su marco de ajustes abierto + public boolean isSettingsFrameOpen(Module module) { + return settingsFrames.stream() + .anyMatch(sf -> sf.module.getName().equals(module.getName())); + } + + // no pausar el juego cuando se abre la interfaz + @Override + public boolean shouldPause() { + return false; + } + + public ModuleFrame getModulesFrame() { + return modulesFrame; + } + + public ClickGUISettingsFrame getGuiSettingsFrame() { + return guiSettingsFrame; + } + + public List getSettingsFrames() { + return settingsFrames; + } + +} diff --git a/src/main/java/me/retucio/camtweaks/ui/HUD.java b/src/main/java/me/retucio/camtweaks/ui/HUD.java new file mode 100644 index 0000000..6439457 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/HUD.java @@ -0,0 +1,13 @@ +package me.retucio.camtweaks.ui; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.render.RenderTickCounter; + +// en un principio esto iba a mostrar en pantalla los módulos activados, pero me parece innecesario. se queda aquí por si acaso +public class HUD { + + private static final MinecraftClient mc = MinecraftClient.getInstance(); + + public static void render(DrawContext ctx, RenderTickCounter tc) {} +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/ui/ModuleButton.java b/src/main/java/me/retucio/camtweaks/ui/ModuleButton.java new file mode 100644 index 0000000..f3334ce --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/ModuleButton.java @@ -0,0 +1,68 @@ +package me.retucio.camtweaks.ui; + +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.util.Colors; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; + +import java.awt.*; + +// clase para el botón para cada módulo +public class ModuleButton { + + public Module module; + public ModuleFrame parent; + public int offset; + public final int height = 18; + + public ModuleButton(Module module, ModuleFrame parent, int offset) { + this.module = module; + this.parent = parent; + this.offset = offset; + } + + public void render(DrawContext ctx, double mouseX, double mouseY, float delta) { + ctx.fill( // dibujar el contorno del botón + parent.x + 2, parent.y + offset + 3, + parent.x + parent.w - 2 , parent.y + height + offset, + determineColor(mouseX, mouseY)); + + ctx.drawTextWithShadow( // dibujar el nombre del módulo + parent.mc.textRenderer, module.getName(), + parent.x + 5, parent.y + offset + (height / 2) - (parent.mc.textRenderer.fontHeight / 2) + 2, -1); + + // dibujar "tooltips" (cajas de texto) al pasar el puntero encima del botón, para mostrar su descripción + if (isHovered((int) mouseX, (int) mouseY)) { + Screen currentScreen = parent.mc.currentScreen; + if (currentScreen != null) + ctx.drawTooltip(Text.of(module.getDescription()), (int) mouseX, (int) mouseY + 20); + } + } + + public void mouseClicked(double mouseX, double mouseY, int button) { + if (isHovered((int) mouseX, (int) mouseY)) { + if (button == 0) // clic izquierdo para activar / desactivar el módulo + module.toggle(); + else if (button == 1) { // clic derecho para el marco de ajustes (también lo cierra si está abierto) + if (ClickGUI.INSTANCE.isSettingsFrameOpen(module)) + ClickGUI.INSTANCE.closeSettingsFrame(module); + else + ClickGUI.INSTANCE.openSettingsFrame(module, + parent.x + parent.w, parent.y + offset); + } + } + } + + // verifica si el puntero del ratón se encuentra sobre el botón del módulo + public boolean isHovered(int mouseX, int mouseY) { + return mouseX > parent.x && mouseX < parent.x + parent.w && mouseY > parent.y + offset && mouseY < parent.y + height + offset; + } + + public int determineColor(double mouseX, double mouseY) { + // determina el color del botón, dependiendo de si está el puntero encima y si está activado + if (module.isEnabled()) + return isHovered((int) mouseX, (int) mouseY) ? Colors.enabledHoveredModuleButtonColor : Colors.enabledModuleButtonColor; + return isHovered((int) mouseX, (int) mouseY) ? Colors.hoveredModuleButtonColor : Colors.moduleButtonColor; + } +} diff --git a/src/main/java/me/retucio/camtweaks/ui/ModuleFrame.java b/src/main/java/me/retucio/camtweaks/ui/ModuleFrame.java new file mode 100644 index 0000000..7b71227 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/ModuleFrame.java @@ -0,0 +1,116 @@ +package me.retucio.camtweaks.ui; + +import me.retucio.camtweaks.CameraTweaks; +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.module.settings.EnumSetting; +import me.retucio.camtweaks.module.settings.KeySetting; +import me.retucio.camtweaks.module.settings.NumberSetting; +import me.retucio.camtweaks.ui.settings.BindButton; +import me.retucio.camtweaks.ui.settings.CycleButton; +import me.retucio.camtweaks.ui.settings.SettingButton; +import me.retucio.camtweaks.ui.settings.SliderButton; +import me.retucio.camtweaks.util.Colors; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; + +import java.util.ArrayList; +import java.util.List; + +// marco para los módulos +public class ModuleFrame { + + public int x, y, w, h, dragX, dragY; + public boolean dragging, extended; + public String title = "módulos -"; + + private final List moduleButtons = new ArrayList<>(); + + protected MinecraftClient mc = MinecraftClient.getInstance(); + + public ModuleFrame(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + dragging = false; + extended = true; + + // determinar el saliente para cada botón de módulo + int offset = h; + for (Module module : CameraTweaks.moduleManager.getModules()) { + moduleButtons.add(new ModuleButton(module, this, offset)); + updateWidth(); + offset += h; + } + } + + void updateWidth() { + // asegurarse de que todos los botones caben en el marco, haciendo que la anchura se ajuste al texto más largo + int maxWidth = mc.textRenderer.getWidth(title); + for (ModuleButton button : moduleButtons) { + String text = button.module.getName(); + int textWidth = mc.textRenderer.getWidth(text); + maxWidth = Math.max(maxWidth, textWidth); + } + this.w = maxWidth + 22; + } + + public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + updateWidth(); + ctx.fill(x, y, x + w, y + h, Colors.frameHeadColor); // cabeza del marco + // título del marco + ctx.drawText(mc.textRenderer, title, + x + (w / 2) - (mc.textRenderer.getWidth(title) / 2), + y + ( h /2) - (mc.textRenderer.fontHeight / 2), + -1, false); + + // dibujar sus módulos solo si está extendido + if (extended) { + int totalHeight = moduleButtons.size() * h + 3; + ctx.fill( // fondo para los botones + x, y + h + 1, + x + w, y + h + totalHeight, + Colors.frameBGColor); + + // dibujar los botones para cada módulo + for (ModuleButton moduleButton : moduleButtons) + moduleButton.render(ctx, mouseX, mouseY, delta); + } + } + + public void mouseClicked(double mouseX, double mouseY, int button) { + // registrar clics + if (isHovered(mouseX, mouseY)) { + if (button == 0) { // clic izquierdo para arrastrarlo + dragging = true; + dragX = (int) (mouseX - x); + dragY = (int) (mouseY - y); + } else if (button == 1) { // clic derecho para extenderlo + extended = !extended; + title = extended ? "módulos -" : "módulos +"; + } + } + + if (!extended) return; // solo dejar clicar en los módulos si el marco está extendido + for (ModuleButton moduleButton : moduleButtons) + moduleButton.mouseClicked(mouseX, mouseY, button); + } + + // detectar cuándo se suelta el clic + public void mouseReleased(double mouseX, double mouseY, int button) { + if (button == 0 && dragging) dragging = false; + } + + // verificar si el puntero del ratón se encuentra encima + public boolean isHovered(double mouseX, double mouseY) { + return mouseX > x && mouseX < x + w && mouseY > y && mouseY < y + h; + } + + // actualizar la posición al arrastrar el marco + public void updatePosition(double mouseX, double mouseY) { + if (dragging) { + x = (int) (mouseX - dragX); + y = (int) (mouseY - dragY); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/ui/settings/BindButton.java b/src/main/java/me/retucio/camtweaks/ui/settings/BindButton.java new file mode 100644 index 0000000..58114ae --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/settings/BindButton.java @@ -0,0 +1,74 @@ +package me.retucio.camtweaks.ui.settings; + +import me.retucio.camtweaks.module.settings.KeySetting; +import me.retucio.camtweaks.util.Colors; +import me.retucio.camtweaks.util.KeyUtil; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; + +// botón para configurar la tecla asignada a un módulo. comienza a escuchar al hacerle clic +public class BindButton extends SettingButton { + + private final KeySetting setting; + private boolean listening = false; + + public BindButton(KeySetting setting, SettingsFrame parent, int offset) { + super(setting, parent, offset); + this.setting = setting; + } + + @Override + public void render(DrawContext ctx, double mouseX, double mouseY, float delta) { + int x = parent.x + 4; + int y = parent.y + offset + 3; + int w = parent.w - 8; + + int bgColor = isHovered((int) mouseX, (int) mouseY) + ? Colors.hoveredCycleButtonColor + : Colors.cycleButtonColor; + + ctx.fill(x, y, x + w, y + height, bgColor); + String label = setting.getName() + ": " + (listening ? "..." : setting.getKeyName()); + ctx.drawTextWithShadow(parent.mc.textRenderer, label, x + 5, y + 3, -1); + + if (isHovered((int) mouseX, (int) mouseY)) { + Screen currentScreen = parent.mc.currentScreen; + if (currentScreen != null) + ctx.drawTooltip(Text.of(setting.getDescription()), (int) mouseX, (int) mouseY + 20); + } + } + + @Override + public void mouseClicked(double mouseX, double mouseY, int button) { + if (isHovered((int) mouseX, (int) mouseY)) { + if (button == 0) listening = !listening; + else if (button == 1 && KeyUtil.isKeyDown(GLFW.GLFW_KEY_LEFT_SHIFT)) + setting.reset(); + } else { + listening = false; + } + } + + public void onKey(int key, int action) { + if (listening && action == GLFW.GLFW_PRESS) { + if (key == GLFW.GLFW_KEY_ESCAPE) + setting.setKey(GLFW.GLFW_KEY_UNKNOWN); + else + setting.setKey(key); + listening = false; + } + } + + public boolean isFocused() { + return listening; + } + + public KeySetting getSetting() { + return setting; + } + + @Override + public void mouseReleased(double mouseX, double mouseY, int button) {} +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/ui/settings/ClickGUISettingsFrame.java b/src/main/java/me/retucio/camtweaks/ui/settings/ClickGUISettingsFrame.java new file mode 100644 index 0000000..a6be623 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/settings/ClickGUISettingsFrame.java @@ -0,0 +1,53 @@ +package me.retucio.camtweaks.ui.settings; + +import me.retucio.camtweaks.module.modules.GUI; +import me.retucio.camtweaks.util.Colors; +import net.minecraft.client.gui.DrawContext; + +public class ClickGUISettingsFrame extends SettingsFrame { + + public static final GUI guiSettings = new GUI(); + private boolean extended = false; + private String title = "ajustes de la interfaz -"; + + public ClickGUISettingsFrame(int x, int y, int w, int h) { + super(guiSettings, x, y, w, h); + } + + @Override + public void mouseClicked(double mouseX, double mouseY, int button) { + if (isHovered(mouseX, mouseY)) { + if (button == 0) { + dragging = true; + dragX = (int) (mouseX - x); + dragY = (int) (mouseY - y); + } else if (button == 1) { + extended = !extended; + title = extended ? "ajustes de la interfaz -" : "ajustes de la interfaz +"; + } + } + + if (!extended) return; // solo dejar clicar en los módulos si el marco está extendido + for (SettingButton settingButton : settingButtons) + settingButton.mouseClicked(mouseX, mouseY, button); + } + + @Override + public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + updateWidth(); + ctx.fill(x, y, x + w, y + h, Colors.frameHeadColor); + + ctx.drawText(mc.textRenderer, title, + x + (w / 2) - (mc.textRenderer.getWidth(title) / 2), + y + (h / 2) - (mc.textRenderer.fontHeight / 2), + -1, false); + + if (extended) { + int totalHeight = settingButtons.size() * h + 3; + ctx.fill(x, y + h + 1, x + w, y + h + totalHeight, Colors.frameBGColor); + + for (SettingButton button : settingButtons) + button.render(ctx, mouseX, mouseY, delta); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/ui/settings/CycleButton.java b/src/main/java/me/retucio/camtweaks/ui/settings/CycleButton.java new file mode 100644 index 0000000..03231aa --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/settings/CycleButton.java @@ -0,0 +1,84 @@ +package me.retucio.camtweaks.ui.settings; + +import me.retucio.camtweaks.module.settings.EnumSetting; +import me.retucio.camtweaks.util.Colors; +import me.retucio.camtweaks.util.KeyUtil; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.List; + +// botón para los ajustes tipo EnumSetting (de modo) +public class CycleButton> extends SettingButton { + + private final EnumSetting setting; + + public CycleButton(EnumSetting setting, SettingsFrame parent, int offset) { + super(setting, parent, offset); + this.setting = setting; + } + + @Override + public void render(DrawContext ctx, double mouseX, double mouseY, float delta) { + int x = parent.x + 4; + int y = parent.y + offset + 3; + int w = parent.w - 8; + + int bgColor = isHovered((int) mouseX, (int) mouseY) + ? Colors.hoveredCycleButtonColor + : Colors.cycleButtonColor; + + ctx.fill(x, y, x + w, y + height, bgColor); // fondo del botón + + // texto del botón: nombre + valor de texto del enum + String label = setting.getName() + ": " + setting.getValue().toString(); + ctx.drawTextWithShadow(parent.mc.textRenderer, label, x + 5, y + 3, -1); + + // tooltip + if (isHovered((int) mouseX, (int) mouseY)) { + Screen currentScreen = parent.mc.currentScreen; + if (currentScreen != null) + // si shift está presionado mientras el puntero está encima, mostrar los modos disponibles + if (GLFW.glfwGetKey(parent.mc.getWindow().getHandle(), GLFW.GLFW_KEY_LEFT_SHIFT) != GLFW.GLFW_RELEASE) { + List lines = new ArrayList<>(); + lines.add(Text.literal("modos disponibles:")); + for (Enum val : setting.getValues()) { + if (val == setting.getValue()) + lines.add(Text.literal("> " + val.toString() + " <").formatted(Formatting.GREEN)); // destacar modo actualmente seleccionado + else + lines.add(Text.literal(val.toString())); + } + ctx.drawTooltip(parent.mc.textRenderer, lines, (int) mouseX, (int) mouseY + 20); + // si shift no está siendo presionado, simplemente mostrar la descripción + } else { + ctx.drawTooltip(Text.of(setting.getDescription()), (int) mouseX, (int) mouseY + 20); + } + } + } + + @Override + public void mouseClicked(double mouseX, double mouseY, int button) { + if (isHovered((int) mouseX, (int) mouseY)) { + if (button == 0) { + setting.cycle(); // clic izquierdo -> ciclar + } else if (button == 1) { + // shift + clic derecho -> restablecer + if (KeyUtil.isKeyDown(GLFW.GLFW_KEY_LEFT_SHIFT)) + setting.reset(); + else + setting.cycleBackwards(); // simplemente clic derecho -> ciclar valores pero hacia atrás + } + } + } + + public EnumSetting getSetting() { + return setting; + } + + @Override + public void mouseReleased(double mouseX, double mouseY, int button) {} +} diff --git a/src/main/java/me/retucio/camtweaks/ui/settings/SettingButton.java b/src/main/java/me/retucio/camtweaks/ui/settings/SettingButton.java new file mode 100644 index 0000000..29bb5e0 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/settings/SettingButton.java @@ -0,0 +1,28 @@ +package me.retucio.camtweaks.ui.settings; + +import me.retucio.camtweaks.module.settings.Setting; +import net.minecraft.client.gui.DrawContext; + +// clase base para los botones de los ajustes +public abstract class SettingButton { + + protected final Setting setting; + protected final SettingsFrame parent; + protected int offset; + protected final int height = 15; + + public SettingButton(Setting setting, SettingsFrame parent, int offset) { + this.setting = setting; + this.parent = parent; + this.offset = offset; + } + + public abstract void render(DrawContext ctx, double mouseX, double mouseY, float delta); + public abstract void mouseClicked(double mouseX, double mouseY, int button); + public abstract void mouseReleased(double mouseX, double mouseY, int button); + + protected boolean isHovered(int mouseX, int mouseY) { + return mouseX > parent.x + 4 && mouseX < parent.x + parent.w - 4 && + mouseY > parent.y + offset && mouseY < parent.y + offset + height; + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/ui/settings/SettingsFrame.java b/src/main/java/me/retucio/camtweaks/ui/settings/SettingsFrame.java new file mode 100644 index 0000000..034a795 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/settings/SettingsFrame.java @@ -0,0 +1,152 @@ +package me.retucio.camtweaks.ui.settings; + +import me.retucio.camtweaks.module.Module; +import me.retucio.camtweaks.module.settings.*; +import me.retucio.camtweaks.ui.ClickGUI; +import me.retucio.camtweaks.util.Colors; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; + +// marco para los botones de los ajustes de cada módulo +public class SettingsFrame { + + private final String title; + public final Module module; + final List settingButtons = new ArrayList<>(); + + public int x, y, w, h; + boolean dragging; + int dragX; + int dragY; + + final MinecraftClient mc = MinecraftClient.getInstance(); + + public SettingsFrame(Module module, int x, int y, int w, int h) { + this.module = module; + this.x = x; + this.y = y; + this.w = w; + this.h = h; + + // añadir el respectivo tipo de botón de cada ajuste a la lista de botones + int offset = h; + for (Setting setting : module.getSettings()) { + if (!setting.isVisible()) continue; + switch (setting) { + case BooleanSetting b -> { + addButton(new ToggleButton(b, this, offset)); + offset += 18; + } case NumberSetting n -> { + addButton(new SliderButton(n, this, offset)); + offset += 18; + } case EnumSetting e -> { + addButton(new CycleButton<>(e, this, offset)); + offset += 18; + } case KeySetting k -> { + addButton(new BindButton(k, this, offset)); + offset += 18; + } case StringSetting s -> { + addButton(new TextButton(s, this, offset)); + offset += 18; + } default -> {} + } + } + title = "ajustes de " + module.getName() + " -"; + } + + public void addButton(SettingButton button) { + settingButtons.add(button); + updateWidth(); // actualizar anchura del frame tras cada iteración + } + + void updateWidth() { + // asegurarse de que todos los botones caben en el marco, haciendo que la anchura se ajuste al texto más largo + int maxWidth = mc.textRenderer.getWidth(title); + for (SettingButton button : settingButtons) { + String text = button.setting.getName(); + + switch (button) { + case SliderButton sliderButton -> text += ": " + sliderButton.df.format((sliderButton.getSetting()).getValue()); + case CycleButton cycleButton -> text += ": " + cycleButton.getSetting().getValue(); + case BindButton bindButton -> text += ": " + bindButton.getSetting().getKeyName(); + case TextButton textButton -> text += ": " + textButton.getSetting().getValue(); + default -> {} + } + + int textWidth = mc.textRenderer.getWidth(text); + maxWidth = Math.max(maxWidth, textWidth); + } + this.w = maxWidth + 22; + } + + public void render(DrawContext ctx, int mouseX, int mouseY, float delta) { + updateWidth(); + ctx.fill(x, y, x + w, y + h, module.isEnabled() + ? Colors.enabledToggleButtonColor.getRGB() + : Colors.disabledToggleButtonColor.getRGB()); + + ctx.drawText(mc.textRenderer, title, + x + (w / 2) - (mc.textRenderer.getWidth(title) / 2), + y + (h / 2) - (mc.textRenderer.fontHeight / 2), + -1, false); + + // si no hay ajustes disponibles para un módulo, hacerle saber al usuario + if (settingButtons.isEmpty()) { + String msg = "no hay ajustes :("; + ctx.fill(x, y + h, x + w, y + 2 * h, Colors.frameBGColor); + + ctx.drawText(mc.textRenderer, msg, + x + (w / 2) - (mc.textRenderer.getWidth(msg) / 2), + (int) (y + h * 1.25), + -1, false); + return; + } + + int totalHeight = settingButtons.size() * h + 3; + ctx.fill(x, y + h + 1, x + w, y + h + totalHeight, Colors.frameBGColor); + + for (SettingButton sb : settingButtons) + if (sb.setting.isVisible()) sb.render(ctx, mouseX, mouseY, delta); + } + + public void mouseClicked(double mouseX, double mouseY, int button) { + if (isHovered(mouseX, mouseY)) { + if (button == 0) { + dragging = true; + dragX = (int) (mouseX - x); + dragY = (int) (mouseY - y); + } else if (button == 1) { + ClickGUI.INSTANCE.closeSettingsFrame(module); + } + } + + for (SettingButton sb : settingButtons) + if (sb.setting.isVisible()) sb.mouseClicked(mouseX, mouseY, button); + } + + public void mouseRelease(double mouseX, double mouseY, int button) { + if (button == 0) dragging = false; + for (SettingButton sb : settingButtons) + if (sb.setting.isVisible()) sb.mouseReleased(mouseX, mouseY, button); + } + + public void updatePosition(double mouseX, double mouseY) { + if (dragging) { + x = (int) (mouseX - dragX); + y = (int) (mouseY - dragY); + } + } + + boolean isHovered(double mouseX, double mouseY) { + return mouseX > x && mouseX < x + w && + mouseY > y && mouseY < y + h; + } + + public List getButtons() { + return settingButtons; + } +} diff --git a/src/main/java/me/retucio/camtweaks/ui/settings/SliderButton.java b/src/main/java/me/retucio/camtweaks/ui/settings/SliderButton.java new file mode 100644 index 0000000..5cb5231 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/settings/SliderButton.java @@ -0,0 +1,76 @@ +package me.retucio.camtweaks.ui.settings; + +import me.retucio.camtweaks.module.settings.NumberSetting; +import me.retucio.camtweaks.util.Colors; +import me.retucio.camtweaks.util.KeyUtil; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; + +import java.awt.*; +import java.text.DecimalFormat; + +// slider (barrita) para los ajustes numéricos +public class SliderButton extends SettingButton { + + private final NumberSetting setting; + private boolean dragging = false; + public final DecimalFormat df; // usar formato decimal para el valor del ajuste + + public SliderButton(NumberSetting setting, SettingsFrame parent, int offset) { + super(setting, parent, offset); + this.setting = setting; + this.df = new DecimalFormat("#.##"); // solo mostrar dos décimales + } + + @Override + public void render(DrawContext ctx, double mouseX, double mouseY, float delta) { + int x = parent.x + 4; + int y = parent.y + offset + 3; + int w = parent.w - 8; + + ctx.fill(x, y, x + w, y + height, Colors.moduleButtonColor); + + // calcular cómo de llena tendría que estar el "slider" + double percent = (setting.getValue() - setting.getMin()) / (setting.getMax() - setting.getMin()); + int filled = (int) (percent * w); + ctx.fill(x, y, x + filled, y + height, Colors.sliderFillingColor); + + String label = setting.getName() + ": " + df.format(setting.getValue()); + ctx.drawTextWithShadow(parent.mc.textRenderer, label, x + 5, y + 3, -1); + + // lógica para el arrastre del valor + if (dragging) { + double newVal = setting.getMin() + ((mouseX - x) / (double) w) * (setting.getMax() - setting.getMin()); + newVal = Math.max(setting.getMin(), Math.min(setting.getMax(), newVal)); + newVal = Math.round(newVal / setting.getIncrement()) * setting.getIncrement(); + setting.setValue((float) newVal); + } + + // dibujar "tooltips" (cajas de texto) al pasar el puntero encima del botón, para mostrar su descripción + if (isHovered((int) mouseX, (int) mouseY)) { + Screen currentScreen = parent.mc.currentScreen; + if (currentScreen != null) + ctx.drawTooltip(Text.of(setting.getDescription()), (int) mouseX, (int) mouseY + 20); + } + } + + @Override + public void mouseClicked(double mouseX, double mouseY, int button) { + if (isHovered((int) mouseX, (int) mouseY)) { + if (button == 0) dragging = true; // arrastrar con el clic izquierdo + else if (button == 1 && KeyUtil.isKeyDown(GLFW.GLFW_KEY_LEFT_SHIFT)) // restablecer al valor por defecto con shift izquerdo + clic derecho + setting.reset(); + } + } + + @Override + public void mouseReleased(double mouseX, double mouseY, int button) { + if (button == 0) dragging = false; + } + + public NumberSetting getSetting() { + return setting; + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/ui/settings/TextButton.java b/src/main/java/me/retucio/camtweaks/ui/settings/TextButton.java new file mode 100644 index 0000000..ee9e546 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/settings/TextButton.java @@ -0,0 +1,97 @@ +package me.retucio.camtweaks.ui.settings; + +import me.retucio.camtweaks.module.settings.StringSetting; +import me.retucio.camtweaks.util.Colors; +import me.retucio.camtweaks.util.KeyUtil; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; + +// botón para los ajustes que requieran introducir texto +public class TextButton extends SettingButton { + + private boolean typing; + private final StringSetting setting; + private final StringBuilder buffer = new StringBuilder(); + + public TextButton(StringSetting setting, SettingsFrame parent, int offset) { + super(setting, parent, offset); + this.setting = setting; + } + + @Override + public void render(DrawContext ctx, double mouseX, double mouseY, float delta) { + int x = parent.x + 4; + int y = parent.y + offset + 3; + int w = parent.w - 8; + int bgColor = isHovered((int) mouseX, (int) mouseY) + ? Colors.hoveredCycleButtonColor + : Colors.cycleButtonColor; + + ctx.fill(x, y, x + w, y + height, bgColor); + + String label = setting.getName() + ": " + (typing ? buffer.toString() + "_" : setting.getValue()); + ctx.drawTextWithShadow(parent.mc.textRenderer, label, x + 5, y + 3, -1); + + if (isHovered((int) mouseX, (int) mouseY)) { + if (parent.mc.currentScreen != null) + ctx.drawTooltip(Text.of(setting.getDescription()), (int) mouseX, (int) mouseY + 20); + } + } + + @Override + public void mouseClicked(double mouseX, double mouseY, int button) { + if (isHovered((int) mouseX, (int) mouseY)) { + if (button == 0) { + typing = true; + buffer.setLength(0); + buffer.append(setting.getValue()); // precargar valor anterior + } else if (button == 1 && KeyUtil.isKeyDown(GLFW.GLFW_KEY_LEFT_SHIFT)) { + typing = false; + setting.reset(); + } + } else { + typing = false; + } + } + + public void onKey(int key, int action) { + if (!typing || action != GLFW.GLFW_PRESS) return; + + switch (key) { // casos para teclas especiales como enter, escape o borrar + case GLFW.GLFW_KEY_ENTER -> { + setting.setValue(buffer.toString()); + typing = false; + } case GLFW.GLFW_KEY_BACKSPACE -> { + buffer.deleteCharAt(buffer.length() - 1); + } case GLFW.GLFW_KEY_ESCAPE -> { + typing = false; + } default -> { + String c = KeyUtil.getKeyName(key); + if (c.length() == 1) { + if (KeyUtil.isKeyDown(GLFW.GLFW_KEY_LEFT_SHIFT) || KeyUtil.isKeyDown(GLFW.GLFW_KEY_RIGHT_SHIFT)) + charTyped(c.toUpperCase().charAt(0)); + else + charTyped(c.toLowerCase().charAt(0)); + } + } + } + } + + private void charTyped(char c) { + if (!typing) return; + buffer.append(c); + } + + public boolean isFocused() { + return typing; + } + + public StringSetting getSetting() { + return setting; + } + + @Override + public void mouseReleased(double mouseX, double mouseY, int button) {} + +} diff --git a/src/main/java/me/retucio/camtweaks/ui/settings/ToggleButton.java b/src/main/java/me/retucio/camtweaks/ui/settings/ToggleButton.java new file mode 100644 index 0000000..9727bd8 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/ui/settings/ToggleButton.java @@ -0,0 +1,63 @@ +package me.retucio.camtweaks.ui.settings; + +import me.retucio.camtweaks.module.settings.BooleanSetting; +import me.retucio.camtweaks.util.Colors; +import me.retucio.camtweaks.util.KeyUtil; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; + +import java.awt.*; + +// botón para los ajustes booleanos (funciona como un interruptor) +public class ToggleButton extends SettingButton { + + private final BooleanSetting setting; + + public ToggleButton(BooleanSetting setting, SettingsFrame parent, int offset) { + super(setting, parent, offset); + this.setting = setting; + } + + @Override + public void render(DrawContext ctx, double mouseX, double mouseY, float delta) { + int x = parent.x + 4; + int y = parent.y + offset + 3; + int w = parent.w - 8; + + Color color = setting.isEnabled() + ? Colors.enabledToggleButtonColor + : Colors.disabledToggleButtonColor; + + if (isHovered((int) mouseX, (int) mouseY)) + color = color.brighter(); // simplemente utilizar el método "built-in" de Java para hacer el botón más claro cuando el puntero se encuentra encima de él + + ctx.fill(x, y, x + w, y + height, color.getRGB()); + ctx.drawTextWithShadow(parent.mc.textRenderer, + setting.getName(), x + 5, y + 3, -1); + + // dibujar "tooltips" (cajas de texto) al pasar el puntero encima del botón, para mostrar su descripción + if (isHovered((int) mouseX, (int) mouseY)) { + Screen currentScreen = parent.mc.currentScreen; + if (currentScreen != null) + ctx.drawTooltip(Text.of(setting.getDescription()), (int) mouseX, (int) mouseY + 20); + } + } + + @Override + public void mouseClicked(double mouseX, double mouseY, int button) { + if (isHovered((int) mouseX, (int) mouseY)) { + if (button == 0) setting.toggle(); // clic izquierdo para alternar + else if (button == 1 && KeyUtil.isKeyDown(GLFW.GLFW_KEY_LEFT_SHIFT)) // shift izquierdo + clic derecho para restablecer al valor por defecto + setting.reset(); + } + } + + public BooleanSetting getSetting() { + return setting; + } + + @Override + public void mouseReleased(double mouseX, double mouseY, int button) {} +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/util/ChatUtil.java b/src/main/java/me/retucio/camtweaks/util/ChatUtil.java new file mode 100644 index 0000000..57e1121 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/util/ChatUtil.java @@ -0,0 +1,60 @@ +package me.retucio.camtweaks.util; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +// cosas útiles relacionadas al chat +public class ChatUtil { + + private static final MinecraftClient mc = MinecraftClient.getInstance(); + + public static void addMessage(String text) { + mc.inGameHud.getChatHud().addMessage(Text.of(text)); + } + + public static void addMessage(Text text) { + mc.inGameHud.getChatHud().addMessage(text); + } + + public static void addMessageWithPrefix(String text) { + addMessageWithPrefix(Text.of(text)); + } + + public static void addMessageWithPrefix(Text text) { + addMessage(Text.literal(Formatting.GREEN + "[smegma] " + Formatting.RESET).append(text)); + } + + public static void info(String text) { + addMessage(Text.literal(Formatting.GREEN + "[smegma] " + Formatting.RESET + text)); + } + + public static void info(Text text) { + addMessage(Text.literal(Formatting.GREEN + "[smegma] " + Formatting.RESET).append(text)); + } + + public static void warn(String text) { + addMessage(Text.literal(Formatting.GREEN + "[smegma] " + Formatting.YELLOW + text)); + } + + public static void warn(Text text) { + addMessage(Text.literal(Formatting.GREEN + "[smegma] " + Formatting.YELLOW).append(text)); + } + + public static void error(String text) { + addMessage(Text.literal(Formatting.GREEN + "[smegma] " + Formatting.RED + text)); + } + + public static void error(Text text) { + addMessage(Text.literal(Formatting.GREEN + "[smegma] " + Formatting.RED).append(text)); + } + + public static void sendServerMessage(String text) { + sendServerMessage(Text.of(text)); + } + + public static void sendServerMessage(Text text) { + if (mc.player == null) return; + mc.player.sendMessage(text, false); + } +} diff --git a/src/main/java/me/retucio/camtweaks/util/Colors.java b/src/main/java/me/retucio/camtweaks/util/Colors.java new file mode 100644 index 0000000..4c91855 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/util/Colors.java @@ -0,0 +1,41 @@ +package me.retucio.camtweaks.util; + +import me.retucio.camtweaks.ui.ClickGUI; + +import java.awt.*; + +import static me.retucio.camtweaks.ui.settings.ClickGUISettingsFrame.guiSettings; + + +// interfaz para los colores, para no repetirme (regla DRY) +// si se ve especificado como color en algún lado "-1", significa blanco +public class Colors { + + public static int frameHeadRed = guiSettings.red.getIntValue(); + public static int frameHeadGreen = guiSettings.green.getIntValue(); + public static int frameHeadBlue = guiSettings.blue.getIntValue(); + public static int frameHeadAlpha = guiSettings.alpha.getIntValue(); + + public static int frameHeadColor = new Color( + frameHeadRed, + frameHeadGreen, + frameHeadBlue, + frameHeadAlpha + ).getRGB(); + + public static int frameBGColor = new Color(60, 60, 60, 70).getRGB(); + + public static int enabledHoveredModuleButtonColor = new Color(20, 20, 255, 180).getRGB(); + public static int enabledModuleButtonColor = new Color(0, 0, 255, 180).getRGB(); + public static int hoveredModuleButtonColor = new Color(30, 30, 30, 100).getRGB(); + public static int moduleButtonColor = new Color(40, 40, 40, 75).getRGB(); + + public static int hoveredCycleButtonColor = new Color(70, 70, 70, 180).getRGB(); + public static int cycleButtonColor = new Color(50, 50, 50, 150).getRGB(); + + public static int sliderFillingColor = new Color(60, 120, 220, 180).getRGB(); + + // necesitan ser de tipo java.awt.Color para utilizar el método .brighter() + public static Color enabledToggleButtonColor = new Color(0, 180, 0, 180); + public static Color disabledToggleButtonColor = new Color(120, 30, 30, 150); +} diff --git a/src/main/java/me/retucio/camtweaks/util/KeyUtil.java b/src/main/java/me/retucio/camtweaks/util/KeyUtil.java new file mode 100644 index 0000000..8dc7f71 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/util/KeyUtil.java @@ -0,0 +1,45 @@ +package me.retucio.camtweaks.util; + +import me.retucio.camtweaks.CameraTweaks; +import org.lwjgl.glfw.GLFW; + +// cosas útiles relacionadas a las teclas +public class KeyUtil { + + public static boolean isKeyDown(int key) { + return GLFW.glfwGetKey( + CameraTweaks.mc.getWindow().getHandle(), + key) != GLFW.GLFW_RELEASE; + } + + public static String getKeyName(int key) { + if (key == GLFW.GLFW_KEY_UNKNOWN) return "ninguna"; + + String name = GLFW.glfwGetKeyName(key, 0); + if (name != null) return name.toUpperCase(); + + // para teclas especiales + return switch (key) { + case GLFW.GLFW_KEY_SPACE -> "espacio"; + case GLFW.GLFW_KEY_LEFT_SHIFT -> "shift izquierdo"; + case GLFW.GLFW_KEY_RIGHT_SHIFT -> "shift derecho"; + case GLFW.GLFW_KEY_LEFT_CONTROL -> "ctrl izquierdo"; + case GLFW.GLFW_KEY_RIGHT_CONTROL -> "ctrl derecho"; + case GLFW.GLFW_KEY_LEFT_ALT -> "alt izquierdo"; + case GLFW.GLFW_KEY_RIGHT_ALT -> "alt gr"; + case GLFW.GLFW_KEY_TAB -> "tab"; + case GLFW.GLFW_KEY_ENTER -> "enter"; + case GLFW.GLFW_KEY_BACKSPACE -> "borrar"; + case GLFW.GLFW_KEY_DELETE -> "suprimir"; + case GLFW.GLFW_KEY_UP -> "arriba"; + case GLFW.GLFW_KEY_DOWN -> "abajo"; + case GLFW.GLFW_KEY_LEFT -> "izquierda"; + case GLFW.GLFW_KEY_RIGHT -> "derecha"; + default -> { // F1–F25 + if (key >= GLFW.GLFW_KEY_F1 && key <= GLFW.GLFW_KEY_F25) + yield "F" + (key - GLFW.GLFW_KEY_F1 + 1); + yield "tecla " + key; + } + }; + } +} diff --git a/src/main/java/me/retucio/camtweaks/util/interfaces/IChatHud.java b/src/main/java/me/retucio/camtweaks/util/interfaces/IChatHud.java new file mode 100644 index 0000000..ca23d89 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/util/interfaces/IChatHud.java @@ -0,0 +1,8 @@ +package me.retucio.camtweaks.util.interfaces; + +import net.minecraft.text.Text; + +public interface IChatHud { + + void smegma$add(Text message, int id); +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/util/interfaces/IChatHudLine.java b/src/main/java/me/retucio/camtweaks/util/interfaces/IChatHudLine.java new file mode 100644 index 0000000..fa5724a --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/util/interfaces/IChatHudLine.java @@ -0,0 +1,14 @@ +package me.retucio.camtweaks.util.interfaces; + +import com.mojang.authlib.GameProfile; + +public interface IChatHudLine { + + String smegma$getText(); + + int smegma$getId(); + void smegma$setId(int id); + + GameProfile smegma$getSender(); + void smegma$setSender(GameProfile profile); +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/util/interfaces/IChatHudLineVisible.java b/src/main/java/me/retucio/camtweaks/util/interfaces/IChatHudLineVisible.java new file mode 100644 index 0000000..3013a07 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/util/interfaces/IChatHudLineVisible.java @@ -0,0 +1,7 @@ +package me.retucio.camtweaks.util.interfaces; + +public interface IChatHudLineVisible extends IChatHudLine { + + boolean smegma$isStartOfEntry(); + void smegma$setStartOfEntry(boolean start); +} \ No newline at end of file diff --git a/src/main/java/me/retucio/camtweaks/util/interfaces/IMessageHandler.java b/src/main/java/me/retucio/camtweaks/util/interfaces/IMessageHandler.java new file mode 100644 index 0000000..531eaa6 --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/util/interfaces/IMessageHandler.java @@ -0,0 +1,8 @@ +package me.retucio.camtweaks.util.interfaces; + +import com.mojang.authlib.GameProfile; + +public interface IMessageHandler { + + GameProfile smegma$getSender(); +} diff --git a/src/main/java/me/retucio/camtweaks/util/interfaces/TextVisitor.java b/src/main/java/me/retucio/camtweaks/util/interfaces/TextVisitor.java new file mode 100644 index 0000000..86990aa --- /dev/null +++ b/src/main/java/me/retucio/camtweaks/util/interfaces/TextVisitor.java @@ -0,0 +1,41 @@ +package me.retucio.camtweaks.util.interfaces; + +import net.minecraft.text.PlainTextContent; +import net.minecraft.text.StringVisitable; +import net.minecraft.text.Style; +import net.minecraft.text.Text; + +import java.util.ArrayDeque; +import java.util.Optional; +import java.util.Queue; + +@FunctionalInterface +public interface TextVisitor { + Optional accept(Text text, Style style, String string); + + static Optional visit(Text text, TextVisitor visitor, Style baseStyle) { + Queue queue = collectSiblings(text); + return text.visit((style, string) -> visitor.accept(queue.remove(), style, string), baseStyle); + } + + /** + * Collapses the tree of {@link Text} siblings into a one dimensional FIFO {@link Queue}. To match the behaviours of + * the {@link Text#visit(StringVisitable.Visitor)} and {@link Text#visit(StringVisitable.StyledVisitor, Style)} + * methods, texts with empty contents (created from {@link Text#empty()}) are ignored but their siblings are still + * processed. + * @param text the text + * @return the text and its siblings in the order they appear when rendered. + */ + static ArrayDeque collectSiblings(Text text) { + ArrayDeque queue = new ArrayDeque<>(); + collectSiblings(text, queue); + return queue; + } + + private static void collectSiblings(Text text, Queue queue) { + if (!(text.getContent() instanceof PlainTextContent ptc) || !ptc.string().isEmpty()) queue.add(text); + for (Text sibling : text.getSiblings()) { + collectSiblings(sibling, queue); + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/camtweaks/icon.png b/src/main/resources/assets/camtweaks/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0d5824350376ead1ae3bb62f89e4963b92c75a03 GIT binary patch literal 4724 zcmeAS@N?(olHy`uVBq!ia0y~yU}ykg4rT@hhA$5${$yZaU@Q)DcVbv~PUa;80|QIC zqpu?a!^VE@KZ&di3{2kxd_r9R|NsB(+qZM)&TZYg_5Ats|Ni~^`}gmkKYxDy{Q2R- zhewYdojP^u*s)`mE?xTi_3QNM)91{YbL7a8hK7dUzkfe|{CNNV{f&){p`oD$1_qLn zl3ZL|%*@P8OiYZ7jH;@tmoHy_|Ni}tA3t8bdbM-s&ZMLyZEbBqK|vN476t|eF)^_h zFJ7EHd2;2-mA`)dx^w5w#EBFA{rz=xbVNl(+1c5xt*xIveOg>xTwY#YTU-15`Ew;D zC015eUS3{VSy@X<%ec6>&d$!&t5>gGySBQzIypJ{&6_u`U%#%atK;M2Q&Caz@bGAB zYy0@|V`*vWk|j$nUcAW9&#$PcXk}$pQd08y^Ji02QxOr7vuDpfeE9I_(W4V4OwiQS zY;JD8cJ11{dGlt^o-HLM)zs8<_3G7`GiT17JNMbMXZP;iQ&v`ra<;xc}HMNkCkVA(Koj7q~-MV#od3o*a?QCpp_wV0-@ZiCW88f`Sy;D+B%+1Yj z-@aW|R_5&NJY~w1ckkZq*|R4rD{IG&9rg9~Jv}{>CQUkU;DCvViIgsZGa%N{|fBN*PtEsdetv6f>x~;X3JVMS z`uYL_0&d>CxqbWgEnBvwrAu&_9I@SuQz zKu}PStE;P?p5E!xrxOwq6ciL(TwF9XH1zfLQ&UqTA|hgAV~vfCOLfnIG7e{fM`SSr z1K(i~W;~w1B87p0L88Ppq9iy!t)x7$D3u`~F*C13&(AeP!Bo#s&q6x$)d~g%hHa@4 zo@t(*S_~Wv3=FIcQjDw&j0_A6UJMKj(oi-iLuxQGgT%V`LW6;UPr=j0F{C2y?X27wk?U6voM$rLy?TQJ_lyI1 zH(VdfO`N(&(9y$%vzyapQU}M0ReRP-ENogJ5YP0H^_Bw%M^;aUf|{Gh6_*WSnM#X7 zI}BCV_I62dcP-hvFmtk9?JYa`dH3heygOI2On2Xt>-xX{A2>IsI{n>D>+gH+G9GAT zX6KW!D0qOv`0uLXcTO+qW}qfOLtW80jq6yL zak_to=kGNaZYDm;mXizF@mxl>{z=CP_4%`;P4nxv$S?YpsrgbkdBKtCwkBUTPMo$e zlzU#K**R;QI5pMBf#+8WhH|&w-D=_?+_H$hxhq-y-us{8aWN*k6D4o@oJn%=>3Z&& z(CKfqWXUI0{w^aH!BX$bJ)K5TSC+(Yt6^1I7R?gU=Ksv1L1+DpoXHEGH!q0kv?$&a zq@%|2)I)8hfb*H&Q!7H%j2vfO3GMD#wC_9rU!g@=TO+s_F2&5ZnebPn&{0V{U`Kl3 ztP7?S<_8FD`KGw;WNJr*h8UB_-EI?xD^f>FZ>~xCyX4WN>E%bTNL~vh?Jm z?03w&-dxqyI&wVa5!;3Q)Hm-fU1ANj4Ow$<$E|Dcn*aCD*5#VJiYlj8oV}y%<=l4s z_0=BUJ&J?%$=3>^}&}neY<`sMC~qEzIw;|8w*7mSC_x7EAF3v^Xs0} z?^8dCG+g<$ceQlbD*@$YZ*Fh+|MqIH(X36?3v)HB)@v%7fBCv(rq=4uH$VGbbvpL& zR@3qWEzIxTuIf~sFIt#){-F7N`zvp@hA)#pe0sr>O-FjB-Qd4Bd*!3*nyXj%`KN2H zP`+a&&FkN&oB6rFF61lQ`>ZA_rJ|#!`kklCpU&vLuB9pMvDMJ;_^rPBKiwV&FJ$c( zR@|TCKf$25!YJHiU8|R0;~TyOfB3KdJ*8~_W3p*+nM*|ag`)kX6Fv3!{CKJ1*Lp_4 z&SX*6vn$gt&9*i@W0c-8GuO}dNRC5~Lf8g7iEZ=rK2E#**zWI!*Jo}v`9|0OJzcCl z`CNa{lXoj>rmtIR_4v{ff%S9OG zOXYPdx)Zc|rdIvaY~L)uC|~=f{1pCA{QGBB|MdEM)412~ckKC?y&CV%xNDo8*(;e{ zz3B4g%Zq6>Qr(`pu%w1s^ioGiTO~jpq|?tz5Z!w@35oiDA;(U9WOF z%7nRDHBMKrG2U{IbxV)<34ZPe_+o~U z!OhbWGA8s$7}Peb*<`t|Tiox~zvubk28UPsZ!8V8+`VMKSHr@e4*Bt?eLeizl+Lv) zSIv0q74T+XuJO^73|?QhfT}Z_*G%e3T>0o(uKqRmvuSOnyU)v>-tl|OM$HfFn;fdL zx9&U9ZP~qW+1B7L8@}Gl3%40vQ+lz@^V#g%kG|AtHf%FGnq}AZBH6hx_e|i5tCh)5 z@=o(?pODGS!5b$QQ>DI9dc~fmTW_qpeq5eb&EW9oQ1;I=zS7%o*4J&?Vbssy5S-ZK zx=3dJ4n&0=F>X2wF)McsfPCEeU_COkM*DO^WCU)3161< zNOOVd%p8^3w-&_JF}a=esqOYJSiE}m_uNde-^!br9#?OX%Rbq-_tu1aX5B~Do}GQB zslnK?RM{-&>6)KEH8PV9Zp}Y4b>psIA;oUOod1+8Sgn37%(t_=a63BHdG3?4;AfsOEj(|7MY`uo;AeD>3A#gZ?y{@?rK zQklvkDH)|%zINvCkV!k0972BoTYIqN`|}eK$Nt3Z^0avopy!dbgYBvO3~8Hx&S~vF zp54W|RSH_u3p+$~uVza#FW+C4{Au6yy27UEZofj~E51c6e%twcY47!$Ihj8uh&7b$ zwXe?$a5&(E3lGPJeLlS2`uT$QJ`djsulxP` z|Che&8zOh|ZYmG&ow0W9wBC0wZEZ#FPUD>LV)JV^h678jUasjEavB)j|`Parv#d{Xsm)W{M;q(4ElZ!>~)~4)B`D<^|c+7aV%c059 z{|c3yY+7Wm?6M7h{^$Mlb0@3MZVg$x_wN38K2j;>ZZjSqab2V2yn5D?*^#TKoxLYI zzq*xom2zm~vRL8&8~to%v2HzAe#%(rDR41s7R$=fB49+Kc6No_)Nce%{o2`n{Xe?!C7&(c)C$ zX%U*fp5JDUu+9AU8)LjAOohGs)HHhXiyxZro_^iA`fdC>m&SA3C7v&?7ZSWL&A6cT zMs$*x)~|N1d%Ulu@0jdW?R~y(lhKX%mBCRlyDIIw9*G2AVa~NG*|~0!2meq14YP0D zQaK{}Z`<3K?^^tKMLO76I9N(Q=j<%cIU?{PsXisJec_t(9;~`I?VoIZD6rdME4$!) z$-QTb{407k%j9v1Z7KNkB|FXFzebsFS=iH~?`#5m92c-T>=L_?h{SCbf6#sA(~bIS)vQy3hF&#C_a=ra{F`=myJyY4*^h5%Jq)V)$*}b_ufyb@ zuXfxGH{Z8Ex^>5oInReUVQm5l7UtK~Pqq$NQpl#h zNa=mxkarD_eYwi@JmUl}{Jh}G0^zxu@(|6@Qe$Au$vWe@xzTj2o zGe7s7EP3_q+rQ6(;hme##r@LWsCDGKe1_l=@r?^Ne!j56B&h!8g#b>@>AwXFD;+io zZ82T>YuSxoOFMq3vTm^BUoy8^ApDl+^y41CC8Zj)^q$-?;Ln_BKV`z6h}Id~R-9mI zXTQBUaf5BUis_uMmlZqS-_&S*&lve;=BC$6TP4kyJPLj5+ z1wIyT3u|57VVs@&%?`H4K(H($f{=>?OTmo|@hzRx+lcJ11= z{*!YLsOPZVZD$KS@J9LGS2l}jA4HQTE!ntp>C&aGnmSi=m~6{dXq)X_C!2M(<9*R_ z#-9wSmibC*g>KmjnvCZ*hBn`5^|Jf4RY)W;IeO2}&0PYaO&yz`R372J0+TBf@-~v zcDm9EtGQRKzuIASPwu^e(yfBUWfobScGdk{(PvWF88z-k-aq}IbA=x3((?=07U<8} z=lQB}=Nah<2ETb{3x+1gGh0sEUvZ zC~}po5$bp)zcnPL!+5)dm00oB=ewTI+}p=-&E?pUn$!P}%B&FBHY2d*sP>}DjfVGj zY`ygLfbz4TFQ)2T4)6aOt~a^5(V8>)aJ_VP!mI_--|jp=YcPMUjeSA&TOnSC*qBdu zkMZyOYccotGTqYOXm{{(PKU@w=H}Qegac35}H@uMWB< zs>Oc#cd~u||1VENzf1JIELc8eO4sW@4i1`bXR06k=ajE|kh@ATUZb+<_t5~03J0Cd z%?bHSPwvV7a^OmO->(8=v1RrNfnN^HQFC1k>6(2={`|jVjsD(be>G`PztPjx&t;uc GLK6V`ecq!0 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/camtweaks/icon_chat.png b/src/main/resources/assets/camtweaks/icon_chat.png new file mode 100644 index 0000000000000000000000000000000000000000..0af96755845983a1827d297ce451dbfd0fad5d56 GIT binary patch literal 3424 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/camtweaks.mixins.json b/src/main/resources/camtweaks.mixins.json new file mode 100644 index 0000000..8b04163 --- /dev/null +++ b/src/main/resources/camtweaks.mixins.json @@ -0,0 +1,33 @@ +{ + "required": true, + "package": "me.retucio.camtweaks.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + "StringHelperMixin", + "accessor.StatusEffectInstanceAccessor" + ], + "injectors": { + "defaultRequire": 1 + }, + "overwrites": { + "requireAnnotations": true + }, + "client": [ + "CameraMixin", + "ChatHudLineMixin", + "ChatHudLineVisibleMixin", + "ChatHudMixin", + "ChatInputSuggestorMixin", + "ChatScreenMixin", + "ClientPlayNetworkHandlerMixin", + "GameOptionsMixin", + "GameRendererMixin", + "HeldItemRendererMixin", + "InGameHudMixin", + "KeyboardMixin", + "LightmapTextureManagerMixin", + "MessageHandlerMixin", + "MinecraftClientMixin", + "MouseMixin" + ] +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..cc2d6ee --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,34 @@ +{ + "schemaVersion": 1, + "id": "camtweaks", + "version": "${version}", + "name": "mod de putas de retucio", + "description": "cositas chulas pa la cámara", + "authors": [ + "prepucio" + ], + "contact": { + "homepage": "https://github.com/retuci0", + "sources": "https://github.com/retuci0/camera-tweaks" + }, + "license": "CC0-1.0", + "icon": "assets/camtweaks/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "me.retucio.camtweaks.CameraTweaks" + ] + }, + "mixins": [ + "camtweaks.mixins.json" + ], + "depends": { + "fabricloader": ">=0.16.14", + "minecraft": "~1.21.6", + "java": ">=21", + "fabric-api": "*" + }, + "suggests": { + "another-mod": "*" + } +} \ No newline at end of file