commit cce703122b05d92e3278fb797759446cdcf3b053 Author: retuci0 Date: Thu Aug 21 16:14:14 2025 +0200 v1.0 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 0000000..1b33c55 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ff23a68 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..23d15a9 --- /dev/null +++ b/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + 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 0000000..0d58243 Binary files /dev/null and b/src/main/resources/assets/camtweaks/icon.png differ 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 0000000..0af9675 Binary files /dev/null and b/src/main/resources/assets/camtweaks/icon_chat.png differ 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