├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── com │ └── minenash │ └── enhanced_attack_indicator │ ├── EnhancedAttackIndicator.java │ ├── config │ ├── Config.java │ ├── MidnightConfig.java │ └── ModMenuEntryPoint.java │ └── mixin │ └── InGameHudMixin.java └── resources ├── assets └── enhanced_attack_indicator │ ├── icon.png │ └── lang │ ├── en_us.json │ ├── fr_fr.json │ └── zh_cn.json ├── enhanced_attack_indicator.mixins.json └── fabric.mod.json /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Automatically build the project and run any configured tests for every push 2 | # and submitted pull request. This can help catch issues that only occur on 3 | # certain platforms or Java versions, and provides a first line of defence 4 | # against bad commits. 5 | 6 | name: build 7 | on: [pull_request, push] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | # Use these Java versions 14 | java: [ 15 | 16 # Minimum supported by Minecraft 16 | ] 17 | # and run on both Linux and Windows 18 | os: [ubuntu-20.04, windows-latest] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - name: checkout repository 22 | uses: actions/checkout@v2 23 | - name: validate gradle wrapper 24 | uses: gradle/wrapper-validation-action@v1 25 | - name: setup jdk ${{ matrix.java }} 26 | uses: actions/setup-java@v1 27 | with: 28 | java-version: ${{ matrix.java }} 29 | - name: make gradle wrapper executable 30 | if: ${{ runner.os != 'Windows' }} 31 | run: chmod +x ./gradlew 32 | - name: build 33 | run: ./gradlew build 34 | - name: capture build artifacts 35 | if: ${{ runner.os == 'Linux' && matrix.java == '16' }} # Only upload artifacts built from LTS java on one OS 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: Artifacts 39 | path: build/libs/ 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | classes/ 7 | 8 | # eclipse 9 | 10 | *.launch 11 | 12 | # idea 13 | 14 | .idea/ 15 | *.iml 16 | *.ipr 17 | *.iws 18 | 19 | # vscode 20 | 21 | .settings/ 22 | .vscode/ 23 | bin/ 24 | .classpath 25 | .project 26 | 27 | # macos 28 | 29 | *.DS_Store 30 | 31 | # fabric 32 | 33 | run/ 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Minenash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enhanced Attack Indicator 2 | 3 | Enhanced Attack Indicator is a **Fabric** and [**NeoForge**](https://github.com/Minenash/Enhanced-Attack-Indicator-Forge) mod for Minecraft 1.16.x-1.21.x that shows the attack indicator progress on more actions. 4 | 5 | Added Actions: 6 | * Breaking blocks 7 | * Bows, crossbow, trident drawing 8 | * Eating foods 9 | * Items with a cooldown (shields, chorus fruit, enderpearls). 10 | * Sleep Progress 11 | * Fullness of Shulker Boxes / Bundles 12 | 13 | Other Features: 14 | * Ability to enable/disable the above individually 15 | * Ability to change the priority of the normal weapon cooldown 16 | * Ability to disable pickaxe and shovel weapon cooldowns 17 | * Ability to disable axe weapon cooldowns 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '1.10-SNAPSHOT' 3 | id 'maven-publish' 4 | } 5 | 6 | sourceCompatibility = JavaVersion.VERSION_17 7 | targetCompatibility = JavaVersion.VERSION_17 8 | 9 | archivesBaseName = project.archives_base_name 10 | version = project.mod_version 11 | group = project.maven_group 12 | 13 | repositories { 14 | maven { 15 | url "https://maven.terraformersmc.com/releases/" 16 | } 17 | } 18 | 19 | dependencies { 20 | // To change the versions see the gradle.properties file 21 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 22 | mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" 23 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 24 | 25 | 26 | // Fabric API. This is technically optional, but you probably want it anyway. 27 | modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" 28 | 29 | modImplementation ("com.terraformersmc:modmenu:${project.modmenu_version}") { 30 | exclude module: "fabric-api" 31 | } 32 | 33 | // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. 34 | // You may need to force-disable transitiveness on them. 35 | } 36 | 37 | processResources { 38 | inputs.property "version", project.version 39 | 40 | filesMatching("fabric.mod.json") { 41 | expand "version": project.version 42 | } 43 | } 44 | 45 | tasks.withType(JavaCompile).configureEach { 46 | // ensure that the encoding is set to UTF-8, no matter what the system default is 47 | // this fixes some edge cases with special characters not displaying correctly 48 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 49 | // If Javadoc is generated, this must be specified in that task too. 50 | it.options.encoding = "UTF-8" 51 | 52 | // Minecraft 1.17 (21w19a) upwards uses Java 16. 53 | it.options.release = 17 54 | } 55 | 56 | java { 57 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 58 | // if it is present. 59 | // If you remove this line, sources will not be generated. 60 | withSourcesJar() 61 | } 62 | 63 | jar { 64 | from("LICENSE") { 65 | rename { "${it}_${project.archivesBaseName}"} 66 | } 67 | } 68 | 69 | // configure the maven publication 70 | publishing { 71 | publications { 72 | mavenJava(MavenPublication) { 73 | // add all the jars that should be included when publishing to maven 74 | artifact(remapJar) { 75 | builtBy remapJar 76 | } 77 | artifact(sourcesJar) { 78 | builtBy remapSourcesJar 79 | } 80 | } 81 | } 82 | 83 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 84 | repositories { 85 | // Add repositories to publish to here. 86 | // Notice: This block does NOT have the same function as the block in the top level. 87 | // The repositories here will be used for publishing your artifact, not for 88 | // retrieving dependencies. 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx1G 3 | 4 | # Fabric Properties 5 | # check these on https://fabricmc.net/develop 6 | minecraft_version=1.21.5 7 | yarn_mappings=1.21.5+build.1 8 | loader_version=0.16.10 9 | 10 | # Mod Properties 11 | mod_version = 1.1.0+1.21.2 12 | maven_group = com.minenash 13 | archives_base_name = enhanced_attack_indicator 14 | 15 | # Dependencies 16 | # currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api 17 | fabric_version=0.119.5+1.21.5 18 | modmenu_version = 14.0.0-rc.2 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minenash/Enhanced-Attack-Indicator/91810524ad2ffd2241e5ac516277aad8e973befc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/minenash/enhanced_attack_indicator/EnhancedAttackIndicator.java: -------------------------------------------------------------------------------- 1 | package com.minenash.enhanced_attack_indicator; 2 | 3 | import net.fabricmc.api.ClientModInitializer; 4 | import com.minenash.enhanced_attack_indicator.config.Config; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.client.network.ClientPlayerEntity; 7 | import net.minecraft.component.DataComponentTypes; 8 | import net.minecraft.item.*; 9 | 10 | import java.util.List; 11 | 12 | public class EnhancedAttackIndicator implements ClientModInitializer { 13 | 14 | @Override 15 | public void onInitializeClient() { 16 | Config.init("enhanced_attack_indicator", Config.class); 17 | } 18 | 19 | public static float getProgress(float weaponProgress) { 20 | 21 | ClientPlayerEntity player = MinecraftClient.getInstance().player; 22 | ItemStack mainHand = player.getMainHandStack(); 23 | ItemStack offHand = player.getOffHandStack(); 24 | 25 | if (Config.weaponCoolDownImportance == Config.WeaponCoolDownImportance.FIRST && weaponProgress < 1.0F) 26 | return weaponCooldown(mainHand.getItem(), weaponProgress); 27 | 28 | if (Config.showSleep) { 29 | int sleep = player.getSleepTimer(); 30 | if (sleep > 0 && sleep <= 100) 31 | return sleep == 100 ? 2.0F : sleep / 100.0F; 32 | } 33 | 34 | if (Config.showBlockBreaking) { 35 | float breakingProgress = MinecraftClient.getInstance().interactionManager.getBlockBreakingProgress(); 36 | 37 | if (breakingProgress > 0) 38 | return breakingProgress / 10; 39 | } 40 | 41 | if (Config.showRangeWeaponDraw) { 42 | ItemStack stack = player.getActiveItem(); 43 | Item item = stack.getItem(); 44 | 45 | if (item == Items.BOW) { 46 | float progress = BowItem.getPullProgress(72000 - player.getItemUseTimeLeft()); 47 | return progress == 1.0F ? 2.0F : progress; 48 | } 49 | if (item == Items.CROSSBOW) { 50 | float progress = (stack.getMaxUseTime(player) - player.getItemUseTimeLeft()) / (float) CrossbowItem.getPullTime(stack, player); 51 | return progress >= 1.0F ? 2.0F : progress; 52 | } 53 | if (item == Items.TRIDENT) { 54 | float progress = (stack.getMaxUseTime(player) - player.getItemUseTimeLeft()) / 10.0F; 55 | return progress >= 1.0F ? 2.0F : progress; 56 | } 57 | } 58 | 59 | if (Config.showFoodAndPotions) { 60 | ItemStack stack = player.getActiveItem(); 61 | Item item = stack.getItem(); 62 | if (item.getComponents().contains(DataComponentTypes.FOOD) || item == Items.POTION) { 63 | float itemCooldown = (float) player.getItemUseTime() / stack.getMaxUseTime(player); 64 | return itemCooldown == 0.0F ? 1.0F : itemCooldown; 65 | } 66 | } 67 | 68 | if (Config.showItemContainerFullness) { 69 | ItemStack stack = player.getMainHandStack(); 70 | var container = stack.get(DataComponentTypes.CONTAINER); 71 | if (container != null) { 72 | List items = container.stream().toList(); 73 | int total = 0; 74 | int maxTotal = 64 * (27-items.size()); 75 | for (ItemStack item : items) { 76 | total += item.getCount(); 77 | maxTotal += item.getMaxCount(); 78 | } 79 | float result = (float) total / maxTotal; 80 | return result == 1.0F ? 2.0F : result; 81 | } 82 | var bundle = stack.get(DataComponentTypes.BUNDLE_CONTENTS); 83 | if (bundle != null) { 84 | int total = 0; 85 | for (ItemStack item : bundle.iterate()) 86 | total += item.getCount(); 87 | return total / 64F; 88 | } 89 | } 90 | 91 | if (Config.weaponCoolDownImportance == Config.WeaponCoolDownImportance.MIDDLE && weaponProgress < 1.0F) 92 | return weaponCooldown(mainHand.getItem(), weaponProgress); 93 | 94 | if (Config.showItemCooldowns) { 95 | float cooldown = player.getItemCooldownManager().getCooldownProgress(offHand, 0); 96 | if (cooldown != 0.0F) 97 | return cooldown; 98 | 99 | cooldown = player.getItemCooldownManager().getCooldownProgress(mainHand, 0); 100 | if (cooldown != 0.0F) 101 | return cooldown; 102 | } 103 | 104 | if (Config.showRangeWeaponDraw && (mainHand.getItem() == Items.CROSSBOW && CrossbowItem.isCharged(mainHand) 105 | || offHand.getItem() == Items.CROSSBOW && CrossbowItem.isCharged(mainHand))) 106 | return 2.0F; 107 | 108 | if (Config.weaponCoolDownImportance == Config.WeaponCoolDownImportance.LAST) 109 | return weaponCooldown(mainHand.getItem(), weaponProgress); 110 | 111 | return 1.0F; 112 | 113 | } 114 | 115 | private static float weaponCooldown(Item item, float weaponProgress) { 116 | if (Config.disablePickaxesAndShovels && (item.getTranslationKey().contains("pickaxe") || item.getTranslationKey().contains("shovel"))) 117 | return 1.0F; 118 | if (Config.disableAxes && item.getTranslationKey().contains("axe") && !item.getTranslationKey().contains("pickaxe")) 119 | return 1.0F; 120 | return weaponProgress; 121 | 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/minenash/enhanced_attack_indicator/config/Config.java: -------------------------------------------------------------------------------- 1 | package com.minenash.enhanced_attack_indicator.config; 2 | 3 | public class Config extends MidnightConfig{ 4 | 5 | public enum WeaponCoolDownImportance { FIRST, MIDDLE, LAST } 6 | 7 | @Entry public static WeaponCoolDownImportance weaponCoolDownImportance = WeaponCoolDownImportance.MIDDLE; 8 | @Entry public static boolean disablePickaxesAndShovels = true; 9 | @Entry public static boolean disableAxes = true; 10 | 11 | @Entry public static boolean showBlockBreaking = true; 12 | @Entry public static boolean showRangeWeaponDraw = true; 13 | @Entry public static boolean showItemCooldowns = true; 14 | @Entry public static boolean showFoodAndPotions = true; 15 | @Entry public static boolean showSleep = true; 16 | @Entry public static boolean showItemContainerFullness = true; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/minenash/enhanced_attack_indicator/config/MidnightConfig.java: -------------------------------------------------------------------------------- 1 | package com.minenash.enhanced_attack_indicator.config; 2 | 3 | import com.google.gson.ExclusionStrategy; 4 | import com.google.gson.FieldAttributes; 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | import net.fabricmc.api.EnvType; 8 | import net.fabricmc.api.Environment; 9 | import net.fabricmc.loader.api.FabricLoader; 10 | import net.minecraft.client.MinecraftClient; 11 | import net.minecraft.client.font.TextRenderer; 12 | import net.minecraft.client.gui.DrawContext; 13 | import net.minecraft.client.gui.Element; 14 | import net.minecraft.client.gui.Selectable; 15 | import net.minecraft.client.gui.screen.Screen; 16 | import net.minecraft.screen.ScreenTexts; 17 | import net.minecraft.client.gui.widget.ButtonWidget; 18 | import net.minecraft.client.gui.widget.ClickableWidget; 19 | import net.minecraft.client.gui.widget.ElementListWidget; 20 | import net.minecraft.client.gui.widget.TextFieldWidget; 21 | import net.minecraft.client.resource.language.I18n; 22 | import net.minecraft.text.Style; 23 | import net.minecraft.text.Text; 24 | import net.minecraft.util.Formatting; 25 | 26 | import java.awt.Color; 27 | import java.lang.annotation.ElementType; 28 | import java.lang.annotation.Retention; 29 | import java.lang.annotation.RetentionPolicy; 30 | import java.lang.annotation.Target; 31 | import java.lang.reflect.Field; 32 | import java.lang.reflect.Modifier; 33 | import java.nio.file.Files; 34 | import java.nio.file.Path; 35 | import java.util.*; 36 | import java.util.List; 37 | import java.util.function.BiFunction; 38 | import java.util.function.Function; 39 | import java.util.function.Predicate; 40 | import java.util.regex.Pattern; 41 | 42 | /** MidnightConfig v2.1.0 by TeamMidnightDust & Motschen 43 | * Single class config library - feel free to copy! 44 | * 45 | * Based on https://github.com/Minenash/TinyConfig 46 | * Credits to Minenash */ 47 | 48 | @SuppressWarnings("unchecked") 49 | public abstract class MidnightConfig { 50 | private static final Pattern INTEGER_ONLY = Pattern.compile("(-?[0-9]*)"); 51 | private static final Pattern DECIMAL_ONLY = Pattern.compile("-?([\\d]+\\.?[\\d]*|[\\d]*\\.?[\\d]+|\\.)"); 52 | private static final Pattern HEXADECIMAL_ONLY = Pattern.compile("(-?[#0-9a-fA-F]*)"); 53 | 54 | private static final List entries = new ArrayList<>(); 55 | 56 | protected static class EntryInfo { 57 | Field field; 58 | Object widget; 59 | int width; 60 | int max; 61 | Map.Entry error; 62 | Object defaultValue; 63 | Object value; 64 | String tempValue; 65 | boolean inLimits = true; 66 | String id; 67 | Text name; 68 | int index; 69 | ClickableWidget colorButton; 70 | } 71 | 72 | public static final Map> configClass = new HashMap<>(); 73 | private static Path path; 74 | 75 | private static final Gson gson = new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).excludeFieldsWithModifiers(Modifier.PRIVATE).addSerializationExclusionStrategy(new HiddenAnnotationExclusionStrategy()).setPrettyPrinting().create(); 76 | 77 | public static void init(String modid, Class config) { 78 | path = FabricLoader.getInstance().getConfigDir().resolve(modid + ".json"); 79 | configClass.put(modid, config); 80 | 81 | for (Field field : config.getFields()) { 82 | EntryInfo info = new EntryInfo(); 83 | if ((field.isAnnotationPresent(Entry.class) || field.isAnnotationPresent(Comment.class)) && !field.isAnnotationPresent(Server.class)) 84 | if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) initClient(modid, field, info); 85 | if (field.isAnnotationPresent(Entry.class)) 86 | try { 87 | info.defaultValue = field.get(null); 88 | } catch (IllegalAccessException ignored) {} 89 | } 90 | try { gson.fromJson(Files.newBufferedReader(path), config); } 91 | catch (Exception e) { write(modid); } 92 | 93 | for (EntryInfo info : entries) { 94 | if (info.field.isAnnotationPresent(Entry.class)) 95 | try { 96 | info.value = info.field.get(null); 97 | info.tempValue = info.value.toString(); 98 | } catch (IllegalAccessException ignored) { 99 | } 100 | } 101 | } 102 | @Environment(EnvType.CLIENT) 103 | private static void initClient(String modid, Field field, EntryInfo info) { 104 | Class type = field.getType(); 105 | Entry e = field.getAnnotation(Entry.class); 106 | info.width = e != null ? e.width() : 0; 107 | info.field = field; 108 | info.id = modid; 109 | 110 | if (e != null) { 111 | if (!e.name().equals("")) info.name = Text.translatable(e.name()); 112 | if (type == int.class) textField(info, Integer::parseInt, INTEGER_ONLY, (int) e.min(), (int) e.max(), true); 113 | else if (type == float.class) textField(info, Float::parseFloat, DECIMAL_ONLY, (float) e.min(), (float) e.max(), false); 114 | else if (type == double.class) textField(info, Double::parseDouble, DECIMAL_ONLY, e.min(), e.max(), false); 115 | else if (type == String.class || type == List.class) { 116 | info.max = e.max() == Double.MAX_VALUE ? Integer.MAX_VALUE : (int) e.max(); 117 | textField(info, String::length, null, Math.min(e.min(), 0), Math.max(e.max(), 1), true); 118 | } else if (type == boolean.class) { 119 | Function func = value -> Text.literal((Boolean) value ? "True" : "False").formatted((Boolean) value ? Formatting.GREEN : Formatting.RED); 120 | info.widget = new AbstractMap.SimpleEntry>(button -> { 121 | info.value = !(Boolean) info.value; 122 | button.setMessage(func.apply(info.value)); 123 | }, func); 124 | } else if (type.isEnum()) { 125 | List values = Arrays.asList(field.getType().getEnumConstants()); 126 | Function func = value -> Text.translatable(modid + ".midnightconfig." + "enum." + type.getSimpleName() + "." + info.value.toString()); 127 | info.widget = new AbstractMap.SimpleEntry>(button -> { 128 | int index = values.indexOf(info.value) + 1; 129 | info.value = values.get(index >= values.size() ? 0 : index); 130 | button.setMessage(func.apply(info.value)); 131 | }, func); 132 | } 133 | } 134 | entries.add(info); 135 | } 136 | 137 | private static void textField(EntryInfo info, Function f, Pattern pattern, double min, double max, boolean cast) { 138 | boolean isNumber = pattern != null; 139 | info.widget = (BiFunction>) (t, b) -> s -> { 140 | s = s.trim(); 141 | if (!(s.isEmpty() || !isNumber || pattern.matcher(s).matches())) return false; 142 | 143 | Number value = 0; 144 | boolean inLimits = false; 145 | info.error = null; 146 | if (!(isNumber && s.isEmpty()) && !s.equals("-") && !s.equals(".")) { 147 | value = f.apply(s); 148 | inLimits = value.doubleValue() >= min && value.doubleValue() <= max; 149 | info.error = inLimits? null : new AbstractMap.SimpleEntry<>(t, Text.literal(value.doubleValue() < min ? 150 | "§cMinimum " + (isNumber? "value" : "length") + (cast? " is " + (int)min : " is " + min) : 151 | "§cMaximum " + (isNumber? "value" : "length") + (cast? " is " + (int)max : " is " + max))); 152 | } 153 | 154 | info.tempValue = s; 155 | t.setEditableColor(inLimits? 0xFFFFFFFF : 0xFFFF7777); 156 | info.inLimits = inLimits; 157 | b.active = entries.stream().allMatch(e -> e.inLimits); 158 | 159 | if (inLimits && info.field.getType() != List.class) 160 | info.value = isNumber? value : s; 161 | else if (inLimits) { 162 | if (((List) info.value).size() == info.index) ((List) info.value).add(""); 163 | ((List) info.value).set(info.index, Arrays.stream(info.tempValue.replace("[", "").replace("]", "").split(", ")).toList().get(0)); 164 | } 165 | 166 | if (info.field.getAnnotation(Entry.class).isColor()) { 167 | if (!s.contains("#")) s = '#' + s; 168 | if (!HEXADECIMAL_ONLY.matcher(s).matches()) return false; 169 | try { 170 | info.colorButton.setMessage(Text.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB()))); 171 | } catch (Exception ignored) {} 172 | } 173 | return true; 174 | }; 175 | } 176 | 177 | public static void write(String modid) { 178 | path = FabricLoader.getInstance().getConfigDir().resolve(modid + ".json"); 179 | try { 180 | if (!Files.exists(path)) Files.createFile(path); 181 | Files.write(path, gson.toJson(configClass.get(modid).getDeclaredConstructor().newInstance()).getBytes()); 182 | } catch (Exception e) { 183 | e.printStackTrace(); 184 | } 185 | } 186 | @Environment(EnvType.CLIENT) 187 | public static Screen getScreen(Screen parent, String modid) { 188 | return new MidnightConfigScreen(parent, modid); 189 | } 190 | @Environment(EnvType.CLIENT) 191 | private static class MidnightConfigScreen extends Screen { 192 | protected MidnightConfigScreen(Screen parent, String modid) { 193 | super(Text.translatable(modid + ".midnightconfig." + "title")); 194 | this.parent = parent; 195 | this.modid = modid; 196 | this.translationPrefix = modid + ".midnightconfig."; 197 | } 198 | private final String translationPrefix; 199 | private final Screen parent; 200 | private final String modid; 201 | private MidnightConfigListWidget list; 202 | private boolean reload = false; 203 | 204 | // Real Time config update // 205 | @Override 206 | public void tick() { 207 | super.tick(); 208 | for (EntryInfo info : entries) { 209 | try {info.field.set(null, info.value);} catch (IllegalAccessException ignored) {} 210 | } 211 | } 212 | private void loadValues() { 213 | try { gson.fromJson(Files.newBufferedReader(path), configClass.get(modid)); } 214 | catch (Exception e) { write(modid); } 215 | 216 | for (EntryInfo info : entries) { 217 | if (info.field.isAnnotationPresent(Entry.class)) 218 | try { 219 | info.value = info.field.get(null); 220 | info.tempValue = info.value.toString(); 221 | } catch (IllegalAccessException ignored) {} 222 | } 223 | } 224 | 225 | private ButtonWidget button(int x, int y, int width, int height, Text text, ButtonWidget.PressAction action) { 226 | return ButtonWidget.builder(text, action).dimensions(x,y,width,height).build(); 227 | } 228 | 229 | @Override 230 | protected void init() { 231 | super.init(); 232 | if (!reload) loadValues(); 233 | 234 | this.addDrawableChild( button(this.width / 2 - 154, this.height - 28, 150, 20, ScreenTexts.CANCEL, button -> { 235 | loadValues(); 236 | Objects.requireNonNull(client).setScreen(parent); 237 | })); 238 | 239 | ButtonWidget done = this.addDrawableChild( button(this.width / 2 + 4, this.height - 28, 150, 20, ScreenTexts.DONE, (button) -> { 240 | for (EntryInfo info : entries) 241 | if (info.id.equals(modid)) { 242 | try { 243 | info.field.set(null, info.value); 244 | } catch (IllegalAccessException ignored) {} 245 | } 246 | write(modid); 247 | Objects.requireNonNull(client).setScreen(parent); 248 | })); 249 | 250 | this.list = new MidnightConfigListWidget(this.client, this.width, this.height - 64, 32, 25); 251 | // if (this.client != null && this.client.world != null) this.list.getRowWidth(false); 252 | this.addSelectableChild(this.list); 253 | for (EntryInfo info : entries) { 254 | if (info.id.equals(modid)) { 255 | Text name = Objects.requireNonNullElseGet(info.name, () -> Text.translatable(translationPrefix + info.field.getName())); 256 | 257 | if (info.widget instanceof Map.Entry) { 258 | Map.Entry> widget = (Map.Entry>) info.widget; 259 | if (info.field.getType().isEnum()) widget.setValue(value -> Text.translatable(translationPrefix + "enum." + info.field.getType().getSimpleName() + "." + info.value.toString())); 260 | this.list.addButton(List.of( button(width - 110, 0,100, 20, widget.getValue().apply(info.value), widget.getKey())), name); 261 | } else if (info.field.getType() == List.class) { 262 | if (!reload) info.index = 0; 263 | TextFieldWidget widget = new TextFieldWidget(textRenderer, width - 160, 0, 150, 20, null); 264 | widget.setMaxLength(info.width); 265 | if (info.index < ((List)info.value).size()) widget.setText((String.valueOf(((List)info.value).get(info.index)))); 266 | else widget.setText(""); 267 | Predicate processor = ((BiFunction>) info.widget).apply(widget, done); 268 | widget.setTextPredicate(processor); 269 | } else if (info.widget != null) { 270 | TextFieldWidget widget = new TextFieldWidget(textRenderer, width - 160, 0, 150, 20, null); 271 | widget.setMaxLength(info.width); 272 | widget.setText(info.tempValue); 273 | Predicate processor = ((BiFunction>) info.widget).apply(widget, done); 274 | widget.setTextPredicate(processor); 275 | if (info.field.getAnnotation(Entry.class).isColor()) { 276 | ButtonWidget colorButton = button(width - 185, 0, 20, 20, Text.literal("⬛"), (button -> {})); 277 | try {colorButton.setMessage(Text.literal("⬛").setStyle(Style.EMPTY.withColor(Color.decode(info.tempValue).getRGB())));} catch (Exception ignored) {} 278 | info.colorButton = colorButton; 279 | } 280 | } else { 281 | this.list.addButton(List.of(),name); 282 | } 283 | } 284 | } 285 | 286 | } 287 | @Override 288 | public void render(DrawContext context, int mouseX, int mouseY, float delta) { 289 | super.render(context,mouseX,mouseY,delta); 290 | this.list.render(context, mouseX, mouseY, delta); 291 | context.drawCenteredTextWithShadow(textRenderer, title, width / 2, 15, 0xFFFFFF); 292 | 293 | for (EntryInfo info : entries) { 294 | if (info.id.equals(modid)) { 295 | if (list.getHoveredButton(mouseX,mouseY).isPresent()) { 296 | ClickableWidget buttonWidget = list.getHoveredButton(mouseX,mouseY).get(); 297 | Text text = ButtonEntry.buttonsWithText.get(buttonWidget); 298 | Text name = Text.translatable(this.translationPrefix + info.field.getName()); 299 | String key = translationPrefix + info.field.getName() + ".tooltip"; 300 | 301 | if (info.error != null && text.equals(name)) context.drawTooltip(textRenderer, info.error.getValue(), mouseX, mouseY); 302 | else if (I18n.hasTranslation(key) && text.equals(name)) { 303 | List list = new ArrayList<>(); 304 | for (String str : I18n.translate(key).split("\n")) 305 | list.add(Text.literal(str)); 306 | context.drawTooltip(textRenderer, list, mouseX, mouseY); 307 | } 308 | } 309 | } 310 | } 311 | } 312 | } 313 | @Environment(EnvType.CLIENT) 314 | public static class MidnightConfigListWidget extends ElementListWidget { 315 | TextRenderer textRenderer; 316 | 317 | public MidnightConfigListWidget(MinecraftClient minecraftClient, int i, int j, int k, int m) { 318 | super(minecraftClient, i, j, k, m); 319 | this.centerListVertically = false; 320 | textRenderer = minecraftClient.textRenderer; 321 | } 322 | 323 | @Override 324 | protected int getScrollbarX() { return this.width -7; } 325 | 326 | public void addButton(List buttons, Text text) { 327 | this.addEntry(ButtonEntry.create(buttons, text)); 328 | } 329 | @Override 330 | public int getRowWidth() { return 10000; } 331 | public Optional getHoveredButton(double mouseX, double mouseY) { 332 | for (ButtonEntry buttonEntry : this.children()) { 333 | if (!buttonEntry.buttons.isEmpty() && buttonEntry.buttons.get(0).isMouseOver(mouseX, mouseY)) { 334 | return Optional.of(buttonEntry.buttons.get(0)); 335 | } 336 | } 337 | return Optional.empty(); 338 | } 339 | } 340 | public static class ButtonEntry extends ElementListWidget.Entry { 341 | private static final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; 342 | public final List buttons; 343 | private final Text text; 344 | private final List children = new ArrayList<>(); 345 | public static final Map buttonsWithText = new HashMap<>(); 346 | 347 | private ButtonEntry(List buttons, Text text) { 348 | if (!buttons.isEmpty()) buttonsWithText.put(buttons.get(0),text); 349 | this.buttons = buttons; 350 | this.text = text; 351 | children.addAll(buttons); 352 | } 353 | public static ButtonEntry create(List buttons, Text text) { 354 | return new ButtonEntry(buttons, text); 355 | } 356 | public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { 357 | buttons.forEach(b -> { b.setY(y); b.render(context, mouseX, mouseY, tickDelta); }); 358 | if (text != null && (!text.getString().contains("spacer") || !buttons.isEmpty())) 359 | context.drawTextWithShadow(textRenderer, text,12,y+5,0xFFFFFF); 360 | } 361 | public List children() {return children;} 362 | public List selectableChildren() {return children;} 363 | } 364 | @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Entry { 365 | int width() default 100; 366 | double min() default Double.MIN_NORMAL; 367 | double max() default Double.MAX_VALUE; 368 | String name() default ""; 369 | boolean isColor() default false; 370 | } 371 | @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Client {} 372 | @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Server {} 373 | @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Comment {} 374 | 375 | public static class HiddenAnnotationExclusionStrategy implements ExclusionStrategy { 376 | public boolean shouldSkipClass(Class clazz) { return false; } 377 | public boolean shouldSkipField(FieldAttributes fieldAttributes) { 378 | return fieldAttributes.getAnnotation(Entry.class) == null; 379 | } 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /src/main/java/com/minenash/enhanced_attack_indicator/config/ModMenuEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.minenash.enhanced_attack_indicator.config; 2 | 3 | import com.terraformersmc.modmenu.api.ConfigScreenFactory; 4 | import com.terraformersmc.modmenu.api.ModMenuApi; 5 | 6 | public class ModMenuEntryPoint implements ModMenuApi { 7 | 8 | @Override 9 | public ConfigScreenFactory getModConfigScreenFactory() { 10 | return parent -> Config.getScreen(parent,"enhanced_attack_indicator"); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/minenash/enhanced_attack_indicator/mixin/InGameHudMixin.java: -------------------------------------------------------------------------------- 1 | package com.minenash.enhanced_attack_indicator.mixin; 2 | 3 | import com.minenash.enhanced_attack_indicator.EnhancedAttackIndicator; 4 | import net.minecraft.client.gui.DrawContext; 5 | import net.minecraft.client.gui.hud.InGameHud; 6 | import net.minecraft.client.network.ClientPlayerEntity; 7 | import net.minecraft.client.render.RenderLayer; 8 | import net.minecraft.client.render.RenderTickCounter; 9 | import net.minecraft.entity.Entity; 10 | import net.minecraft.util.Identifier; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Unique; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.Redirect; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | @Mixin(InGameHud.class) 19 | public class InGameHudMixin { 20 | 21 | @Unique boolean renderFullness = false; 22 | 23 | @Redirect(method = "renderCrosshair", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;getAttackCooldownProgress(F)F")) 24 | private float setBarProgress(ClientPlayerEntity player, float baseTime) { 25 | 26 | float progress = EnhancedAttackIndicator.getProgress(player.getAttackCooldownProgress(baseTime)); 27 | 28 | if (progress == 2.0F) 29 | renderFullness = true; 30 | 31 | return progress == 2.0F ? 1.0F : progress; 32 | 33 | } 34 | 35 | @Redirect(method = "renderCrosshair", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;isAlive()Z")) 36 | private boolean dontShowPlus(Entity _entity) { 37 | return false; 38 | } 39 | 40 | private static final Identifier CROSSHAIR_ATTACK_INDICATOR_FULL_TEXTURE = Identifier.of("hud/crosshair_attack_indicator_full"); 41 | @Inject(method = "renderCrosshair", at = @At(value = "TAIL")) 42 | private void showPlus(DrawContext context, RenderTickCounter tickDelta, CallbackInfo info) { 43 | if (renderFullness) { 44 | int j = context.getScaledWindowHeight() / 2 - 7 + 16; 45 | int k = context.getScaledWindowWidth() / 2 - 8; 46 | context.drawGuiTexture(RenderLayer::getCrosshair, CROSSHAIR_ATTACK_INDICATOR_FULL_TEXTURE, k, j, 16, 16); 47 | renderFullness = false; 48 | } 49 | } 50 | 51 | @Redirect(method = "renderHotbar", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;getAttackCooldownProgress(F)F")) 52 | private float setHotBarProgress(ClientPlayerEntity player, float baseTime) { 53 | float progress = EnhancedAttackIndicator.getProgress(player.getAttackCooldownProgress(baseTime)); 54 | return progress == 2.0F ? 0.99F : progress; 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/resources/assets/enhanced_attack_indicator/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minenash/Enhanced-Attack-Indicator/91810524ad2ffd2241e5ac516277aad8e973befc/src/main/resources/assets/enhanced_attack_indicator/icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/enhanced_attack_indicator/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "enhanced_attack_indicator.midnightconfig.title": "Enhanced Attack Indicator", 3 | "enhanced_attack_indicator.midnightconfig.showBlockBreaking": "Show Block Breaking Progress", 4 | "enhanced_attack_indicator.midnightconfig.showRangeWeaponDraw": "Show Ranged Weapon Draw", 5 | "enhanced_attack_indicator.midnightconfig.showItemCooldowns": "Show Item Cooldowns", 6 | "enhanced_attack_indicator.midnightconfig.showFoodAndPotions": "Show Food & Potions", 7 | "enhanced_attack_indicator.midnightconfig.disablePickaxesAndShovels": "Disable Pickaxe's & Shovel's Attack Cooldown", 8 | "enhanced_attack_indicator.midnightconfig.disableAxes": "Disable Axe's Attack Cooldown", 9 | "enhanced_attack_indicator.midnightconfig.weaponCoolDownImportance": "Attack Cooldown Priority", 10 | "enhanced_attack_indicator.midnightconfig.showSleep": "Show Sleep Progress", 11 | "enhanced_attack_indicator.midnightconfig.showItemContainerFullness": "Show Shulker Fullness", 12 | "enhanced_attack_indicator.midnightconfig.enum.WeaponCoolDownImportance.FIRST": "First", 13 | "enhanced_attack_indicator.midnightconfig.enum.WeaponCoolDownImportance.MIDDLE": "Middle", 14 | "enhanced_attack_indicator.midnightconfig.enum.WeaponCoolDownImportance.LAST": "Last" 15 | 16 | } -------------------------------------------------------------------------------- /src/main/resources/assets/enhanced_attack_indicator/lang/fr_fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "enhanced_attack_indicator.midnightconfig.title": "Indicateur d'attaque amélioré", 3 | "enhanced_attack_indicator.midnightconfig.showBlockBreaking": "Afficher la progression de destruction des blocs", 4 | "enhanced_attack_indicator.midnightconfig.showRangeWeaponDraw": "Afficher la préparation des armes à distance", 5 | "enhanced_attack_indicator.midnightconfig.showItemCooldowns": "Afficher les temps de recharge des objets", 6 | "enhanced_attack_indicator.midnightconfig.showFoodAndPotions": "Afficher la nourriture et les potions", 7 | "enhanced_attack_indicator.midnightconfig.disablePickaxesAndShovels": "Désactiver le temps de recharge des pioches et des pelles", 8 | "enhanced_attack_indicator.midnightconfig.disableAxes": "Désactiver le temps de recharge des haches", 9 | "enhanced_attack_indicator.midnightconfig.weaponCoolDownImportance": "Priorité du temps de recharge de l'attaque", 10 | "enhanced_attack_indicator.midnightconfig.showSleep": "Afficher la progression du sommeil", 11 | "enhanced_attack_indicator.midnightconfig.showItemContainerFullness": "Afficher la plénitude de la boîte Shulker", 12 | "enhanced_attack_indicator.midnightconfig.enum.WeaponCoolDownImportance.FIRST": "Premier", 13 | "enhanced_attack_indicator.midnightconfig.enum.WeaponCoolDownImportance.MIDDLE": "Milieu", 14 | "enhanced_attack_indicator.midnightconfig.enum.WeaponCoolDownImportance.LAST": "Dernier" 15 | } -------------------------------------------------------------------------------- /src/main/resources/assets/enhanced_attack_indicator/lang/zh_cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "enhanced_attack_indicator.midnightconfig.title": "Enhanced Attack Indicator", 3 | "enhanced_attack_indicator.midnightconfig.showBlockBreaking": "显示破坏方块进度", 4 | "enhanced_attack_indicator.midnightconfig.showRangeWeaponDraw": "显示远程武器的拉动", 5 | "enhanced_attack_indicator.midnightconfig.showItemCooldowns": "显示物品冷却时间", 6 | "enhanced_attack_indicator.midnightconfig.showFoodAndPotions": "显示食物和药水", 7 | "enhanced_attack_indicator.midnightconfig.disablePickaxesAndShovels": "禁用镐子和锹的攻击冷却时间显示", 8 | "enhanced_attack_indicator.midnightconfig.disableAxes": "禁用斧头的攻击冷却时间显示", 9 | "enhanced_attack_indicator.midnightconfig.weaponCoolDownImportance": "攻击冷却时间优先级", 10 | "enhanced_attack_indicator.midnightconfig.enum.WeaponCoolDownImportance.FIRST": "最高", 11 | "enhanced_attack_indicator.midnightconfig.enum.WeaponCoolDownImportance.MIDDLE": "中等", 12 | "enhanced_attack_indicator.midnightconfig.enum.WeaponCoolDownImportance.LAST": "最后", 13 | "enhanced_attack_indicator.midnightconfig.showSleep": "显示睡眠进度", 14 | "enhanced_attack_indicator.midnightconfig.showItemContainerFullness": "显示潜影盒充满度" 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/enhanced_attack_indicator.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "com.minenash.enhanced_attack_indicator.mixin", 5 | "compatibilityLevel": "JAVA_17", 6 | "client": [ 7 | "InGameHudMixin" 8 | ], 9 | "injectors": { 10 | "defaultRequire": 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "enhanced_attack_indicator", 4 | "version": "${version}", 5 | 6 | "name": "Enhanced Attack Indicator", 7 | "description": "Uses the attack indicator for more than just melee attacks", 8 | "authors": [ 9 | "Minenash" 10 | ], 11 | "contact": { 12 | "homepage": "https://github.com/Minenash/Enhanced-Attack-Indicator", 13 | "sources": "https://github.com/Minenash/Enhanced-Attack-Indicator", 14 | "issues": "https://github.com/Minenash/Enhanced-Attack-Indicator/issues" 15 | }, 16 | 17 | "license": "MIT", 18 | 19 | "environment": "client", 20 | "entrypoints": { 21 | "client": [ "com.minenash.enhanced_attack_indicator.EnhancedAttackIndicator" ], 22 | "modmenu": [ "com.minenash.enhanced_attack_indicator.config.ModMenuEntryPoint" ] 23 | }, 24 | "mixins": [ 25 | "enhanced_attack_indicator.mixins.json" 26 | ], 27 | "depends": { 28 | "fabricloader": "*", 29 | "fabric": "*", 30 | "minecraft": ">=1.21.2", 31 | "java": ">=17" 32 | } 33 | } 34 | --------------------------------------------------------------------------------