diff --git a/build.gradle b/build.gradle index 1279f15..4825892 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.2-SNAPSHOT' + id 'fabric-loom' version '1.9.2' id 'maven-publish' } @@ -16,13 +16,17 @@ repositories { // 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. + maven { + name = "SpongePowered" + url = "https://repo.spongepowered.org/maven" + } } loom { splitEnvironmentSourceSets() mods { - "com.lynzl.advancedcrosshair" { + "advancedcrosshair" { sourceSet sourceSets.client } } @@ -38,7 +42,8 @@ dependencies { // Fabric API. This is technically optional, but you probably want it anyway. modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - + // modImplementation "org.spongepowered:mixin:0.8.7" + // Uncomment the following line to enable the deprecated Fabric API modules. // These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time. diff --git a/gradle.properties b/gradle.properties index 55e56b5..40d96e7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,17 +1,17 @@ -# Done to increase the memory available to gradle. +# Gradle org.gradle.jvmargs=-Xmx1G org.gradle.parallel=true # Fabric Properties -# check these on https://fabricmc.net/develop -minecraft_version=1.20.1 -yarn_mappings=1.20.1+build.1 -loader_version=0.14.21 +minecraft_version=1.21.4 +yarn_mappings=1.21.4+build.1 +loader_version=0.16.9 +loom_version=1.9 # Mod Properties -mod_version=1.0.0 +mod_version=1.2.3 maven_group=com.example archives_base_name=advancedcrosshair # Dependencies -fabric_version=0.83.0+1.20.1 \ No newline at end of file +fabric_version=0.119.4+1.21.4 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a7..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d..94113f2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cb..23d15a9 100644 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# 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/. @@ -83,7 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# 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 @@ -111,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -130,10 +133,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + 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. @@ -141,7 +147,7 @@ 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=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -149,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,16 +204,16 @@ 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 $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# 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" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @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 ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +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 @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%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 diff --git a/src/client/java/advancedcrosshair/mixin/InGameHudMixin.java b/src/client/java/advancedcrosshair/mixin/InGameHudMixin.java index d9d7a69..2d08564 100644 --- a/src/client/java/advancedcrosshair/mixin/InGameHudMixin.java +++ b/src/client/java/advancedcrosshair/mixin/InGameHudMixin.java @@ -1,17 +1,17 @@ package advancedcrosshair.mixin; +import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.hud.InGameHud; import net.minecraft.client.option.GameOptions; +import net.minecraft.client.render.RenderTickCounter; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.effect.StatusEffects; // Import for StatusEffects -import net.minecraft.util.Identifier; +import net.minecraft.entity.effect.StatusEffects; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.math.Box; import net.minecraft.util.math.Vec3d; -import com.mojang.blaze3d.systems.RenderSystem; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -21,237 +21,133 @@ @Mixin(InGameHud.class) public class InGameHudMixin { - @Shadow - private MinecraftClient client; + @Shadow private MinecraftClient client; - private static final Identifier GUI_ICONS_TEXTURE = new Identifier("textures/gui/icons.png"); - - @Inject(method = "renderCrosshair", at = @At("HEAD"), cancellable = true) - private void changeCrosshairColor(DrawContext context, CallbackInfo ci) { - // Ensure client, player, and world are initialized + @Inject( + method = "renderCrosshair", + at = @At("HEAD"), + cancellable = true + ) + private void changeCrosshairColor(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) { if (client == null || client.player == null || client.world == null) { return; } GameOptions options = client.options; - // Ensure options is not null - if (options == null) { + if (options == null || !options.getPerspective().isFirstPerson()) { return; } - if (options.getPerspective().isFirstPerson()) { - // Cancel the original crosshair rendering - ci.cancel(); - - // Check if crosshairs are enabled - if (options.hudHidden) { - return; - } - - // Get screen center - int centerX = context.getScaledWindowWidth() / 2; - int centerY = context.getScaledWindowHeight() / 2; - - // Determine crosshair color based on conditions - boolean isCriticalHitReady = isReadyForCriticalHit(); - boolean isAttackReady = !isCriticalHitReady && isLookingAtLivingEntityWithReadyAttack(); // Only check for red if not already blue - - // Always enable blending first - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - - if (isCriticalHitReady) { - RenderSystem.setShaderColor(0.0F, 0.0F, 1.0F, 1.0F); // Blue for critical hit ready (solid) - } else if (isAttackReady) { - RenderSystem.setShaderColor(1.0F, 0.0F, 0.0F, 1.0F); // Red for attack ready (solid) - } else { - // White crosshair with transparency for vanilla blending effect - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 0.75F); // Semi-transparent white - } + if (options.hudHidden) { + return; + } - // Draw the crosshair using the current color - // This respects resource pack changes to the crosshair texture - context.drawTexture(GUI_ICONS_TEXTURE, centerX - 7, centerY - 7, 0, 0, 15, 15); + // Stop the default crosshair from rendering. + ci.cancel(); - // Render attack indicator if enabled - // Enable blending for attack indicator (it should always blend like vanilla) - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - renderAttackIndicator(context, centerX, centerY); + // Decide the crosshair color. + boolean isCriticalHitReady = isReadyForCriticalHit(); + boolean isAttackReady = !isCriticalHitReady && isLookingAtLivingEntityWithReadyAttack(); - // Reset shader color - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); // Reset to white - // Always disable blend at the end to clean up - RenderSystem.disableBlend(); + int crosshairColor; + if (isCriticalHitReady) { + crosshairColor = 0xFF0080FF; // Crit color + } else if (isAttackReady) { + crosshairColor = 0xFFFF3333; // Attackable color + } else { + crosshairColor = 0xBFFFFFFF; // Default color } - } - private void renderAttackIndicator(DrawContext context, int centerX, int centerY) { - // Ensure client, player, and options are not null - if (client == null || client.player == null || client.options == null) { - return; - } - GameOptions options = client.options; + // Draw the new crosshair and attack indicator. + renderColoredCrosshair(context, crosshairColor); + renderAttackIndicator(context); + } - if (options.getAttackIndicator().getValue() == net.minecraft.client.option.AttackIndicator.CROSSHAIR) { - float attackCooldown = client.player.getAttackCooldownProgress(0.0F); - boolean isReady = false; - - // The 'isReady' for the indicator itself should still be based on 100% cooldown for vanilla behavior, - // or you could adjust this threshold as well if desired for the indicator's readiness state. - if (client.targetedEntity instanceof LivingEntity livingTarget && attackCooldown >= 1.0F) { - // Check if the player's attack cooldown per tick is significant enough for "readiness" - // and the target is not in its hurt (invulnerability) phase. - isReady = client.player.getAttackCooldownProgressPerTick() > 5.0F; - isReady = isReady & livingTarget.hurtTime <= 0; - } - - // Determine texture Y-coordinate for attack indicator - // 68 is typically for "ready" or "charged" state, 94 for "charging" or default. - int textureY = isReady ? 68 : 94; + private void renderColoredCrosshair(DrawContext context, int color) { + int centerX = context.getScaledWindowWidth() / 2; + int centerY = context.getScaledWindowHeight() / 2; - if (attackCooldown < 1.0F) { - // Draw the charging progress bar - int width = (int)(attackCooldown * 17.0F); - // The charging bar typically uses texture Y=94. - context.drawTexture(GUI_ICONS_TEXTURE, centerX - 8, centerY - 7 + 16, 36, 94, width, 4); - } - // If you want to draw the "fully charged" icon (like the small sword/chevrons) - // when attackCooldown >= 1.0F and isReady: - // context.drawTexture(GUI_ICONS_TEXTURE, centerX - 8, centerY - 7 + 16, 36, textureY, 9, 4); // Example values, adjust U, width, height - } + int crosshairSize = 4; + int thickness = 1; + int gap = 0; + + // Horizontal line + context.fill(centerX - crosshairSize - gap, centerY, + centerX - gap, centerY + thickness, color); + context.fill(centerX + gap, centerY, + centerX + crosshairSize + gap + 1, centerY + thickness, color); + + // Vertical line + context.fill(centerX, centerY - crosshairSize - gap, + centerX + thickness, centerY - gap, color); + context.fill(centerX, centerY + gap, + centerX + thickness, centerY + crosshairSize + gap + 1, color); } - // New method to check for critical hit conditions private boolean isReadyForCriticalHit() { - if (client == null || client.player == null || client.world == null || client.interactionManager == null) { + if (client == null || client.player == null || client.world == null) { return false; } - float attackCooldown = client.player.getAttackCooldownProgress(0.0F); + // Velocity provides instant client-side feedback for falling state. + boolean isFalling = client.player.getVelocity().y < 0.0D + && !client.player.isOnGround() + && !client.player.isClimbing() + && !client.player.isTouchingWater(); // More reliable than isSwimming(). - // 1. Attack must be fully charged for a guaranteed strong hit with crit - if (attackCooldown < 1.0F) { - return false; - } + if (!isFalling) return false; + + // Check for conditions that prevent critical hits. + if (client.player.hasStatusEffect(StatusEffects.BLINDNESS)) return false; + if (client.player.hasVehicle()) return false; + if (client.player.isSprinting()) return false; - // 2. Player must be falling - // Check for negative Y-velocity to ensure active descent - if (client.player.getVelocity().y >= 0.0D || client.player.isOnGround() || client.player.isClimbing() || client.player.isSwimming()) { - return false; - } + return isLookingAtValidTarget(); + } - // 3. Player must not be on a ladder/vine (covered by isClimbing()) - // 4. Player must not be in water (covered by isSwimming() and getVelocity().y >= 0.0D) - // 5. Player must not be affected by Blindness - if (client.player.hasStatusEffect(StatusEffects.BLINDNESS)) { + private boolean isLookingAtValidTarget() { + if (client == null || client.player == null) { return false; } - // 6. Player must not be riding an entity - if (client.player.hasVehicle()) { - return false; + if (client.targetedEntity instanceof LivingEntity livingTarget) { + // hurtTime is ignored so the color doesn't flicker on hit. + return livingTarget.isAlive(); } - // 7. Player must not be sprinting (Java Edition specific crit condition) - if (client.player.isSprinting()) { - return false; - } + return false; + } - // 8. Check if looking at a valid LivingEntity - return isLookingAtValidTarget(1.0F); // Use 1.0F cooldown as it's for critical hit + private boolean isLookingAtLivingEntityWithReadyAttack() { + // Simple check for a valid target. + return isLookingAtValidTarget(); } - // Helper method to check if looking at a living entity with a given cooldown threshold - private boolean isLookingAtValidTarget(float minAttackCooldown) { - if (client == null || client.player == null || client.world == null || client.interactionManager == null) { - return false; + private void renderAttackIndicator(DrawContext context) { + if (client == null || client.player == null) { + return; } float attackCooldown = client.player.getAttackCooldownProgress(0.0F); - if (attackCooldown < minAttackCooldown) { - return false; + if (attackCooldown >= 1.0F) { + return; // No indicator needed if attack is fully charged. } - // Force 3 block reach limit regardless of gamemode (except creative/spectator) - double reachDistance = 3.0D; - - // Get player's eye position and look direction - Vec3d eyePosition = client.player.getCameraPosVec(1.0f); - Vec3d lookVector = client.player.getRotationVec(1.0f); - Vec3d traceEnd = eyePosition.add(lookVector.multiply(reachDistance)); - - EntityHitResult entityHitResult = projectEntities(client.player, reachDistance); + int centerX = context.getScaledWindowWidth() / 2; + int centerY = context.getScaledWindowHeight() / 2; - if (entityHitResult != null && entityHitResult.getEntity() instanceof LivingEntity livingEntity) { - if (livingEntity.isAlive() && livingEntity.hurtTime <= 0) { - // Double-check: calculate actual distance to the hit point - double actualDistance = eyePosition.distanceTo(entityHitResult.getPos()); - if (actualDistance <= 3.0D) { - return true; - } - } - } - - // Fallback: check client.targetedEntity but verify it's within 3 blocks - if (client.targetedEntity instanceof LivingEntity livingTargeted && - livingTargeted.isAlive() && - livingTargeted.hurtTime <= 0) { - - // Calculate distance from player's eye to the entity's center - Vec3d entityCenter = livingTargeted.getPos().add(0, livingTargeted.getHeight() / 2, 0); - double distanceToEntity = eyePosition.distanceTo(entityCenter); - - // Also check distance to the entity's bounding box edges - Box entityBox = livingTargeted.getBoundingBox(); - double distanceToBox = entityBox.getCenter().distanceTo(eyePosition); - - // Use the smaller distance (more accurate for hit detection) - double finalDistance = Math.min(distanceToEntity, distanceToBox); - - if (finalDistance <= 3.0D) { - return true; - } - } + int indicatorWidth = 16; + int indicatorHeight = 2; + int indicatorOffset = 10; - return false; - } + int x = centerX - indicatorWidth / 2; + int y = centerY + indicatorOffset; - // Renamed and refactored the original method to use the new helper - private boolean isLookingAtLivingEntityWithReadyAttack() { - // Red crosshair condition: attack at least 91% charged and looking at an attackable entity - return isLookingAtValidTarget(1.0F); - } - - /** - * Helper method to perform a raycast and find the first entity intersected. - * This is similar to how vanilla Minecraft determines targeted entities. - * @param entity The entity performing the raycast (usually the player). - * @param maxDistance The maximum distance for the raycast. - * @return EntityHitResult if an entity is hit, otherwise null. - */ - private EntityHitResult projectEntities(Entity entity, double maxDistance) { - // Ensure the entity performing the raycast is not null - if (entity == null) { - return null; - } - - Vec3d eyePosition = entity.getCameraPosVec(1.0f); // Eye position of the entity - Vec3d lookVector = entity.getRotationVec(1.0f); // Direction the entity is looking - Vec3d traceEnd = eyePosition.add(lookVector.multiply(maxDistance)); // End point of the raycast - - // Define the bounding box for the raycast search area - Box searchBox = entity.getBoundingBox().stretch(lookVector.multiply(maxDistance)).expand(1.0D, 1.0D, 1.0D); - - // Perform the raycast using ProjectileUtil - // The predicate checks if the entity is not a spectator and is attackable - return net.minecraft.entity.projectile.ProjectileUtil.raycast( - entity, // The entity performing the cast - eyePosition, // Start of the ray - traceEnd, // End of the ray - searchBox, // Bounding box to check for entities - (entityToTest) -> !entityToTest.isSpectator() && entityToTest.isAttackable(), // Predicate to filter entities - maxDistance * maxDistance // Squared maximum distance (ProjectileUtil often uses squared distances) - ); + // Background of the indicator bar. + context.fill(x, y, x + indicatorWidth, y + indicatorHeight, 0x80808080); + + // Foreground showing cooldown progress. + int progressWidth = (int) (indicatorWidth * attackCooldown); + context.fill(x, y, x + progressWidth, y + indicatorHeight, 0xFFFFFFFF); } -} \ No newline at end of file +} diff --git a/src/client/resources/fabric.mod.json b/src/client/resources/fabric.mod.json index dd04bd7..52355cb 100644 --- a/src/client/resources/fabric.mod.json +++ b/src/client/resources/fabric.mod.json @@ -27,7 +27,7 @@ ], "depends": { "fabricloader": ">=0.14.21", - "minecraft": "~1.20.1", + "minecraft": "~1.21", "java": ">=17", "fabric-api": "*" },