diff --git a/build.gradle b/build.gradle index 1da3f8b..4b81689 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '1.7-SNAPSHOT' + id 'fabric-loom' version '1.10-SNAPSHOT' } version = "${project.mod_version}-${project.minecraft_version}" diff --git a/gradle.properties b/gradle.properties index fa07812..4971977 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,13 +2,13 @@ org.gradle.jvmargs=-Xmx1G # Fabric Properties -minecraft_version=1.21 -yarn_mappings=1.21+build.9 -loader_version=0.15.11 +minecraft_version=1.21.4 +yarn_mappings=1.21.4+build.8 +loader_version=0.16.14 # Mod Properties mod_version = 1.1.1 archives_base_name = PlayerHealthIndicators # Dependencies -fabric_version=0.100.7+1.21 +fabric_version=0.119.3+1.21.4 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0d18421..d6e308a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/me/andrew/healthindicators/mixin/EntityRendererAccessor.java b/src/main/java/me/andrew/healthindicators/mixin/EntityRendererAccessor.java new file mode 100644 index 0000000..a4110db --- /dev/null +++ b/src/main/java/me/andrew/healthindicators/mixin/EntityRendererAccessor.java @@ -0,0 +1,12 @@ +package me.andrew.healthindicators.mixin; + +import net.minecraft.client.render.entity.EntityRenderDispatcher; +import net.minecraft.client.render.entity.EntityRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(EntityRenderer.class) +public interface EntityRendererAccessor { + @Accessor("dispatcher") + EntityRenderDispatcher getDispatcher(); +} diff --git a/src/main/java/me/andrew/healthindicators/mixin/LivingEntityRenderMixin.java b/src/main/java/me/andrew/healthindicators/mixin/LivingEntityRenderMixin.java new file mode 100644 index 0000000..577ae8e --- /dev/null +++ b/src/main/java/me/andrew/healthindicators/mixin/LivingEntityRenderMixin.java @@ -0,0 +1,169 @@ +package me.andrew.healthindicators.mixin; + +import com.mojang.blaze3d.systems.RenderSystem; +import me.andrew.healthindicators.Config; +import me.andrew.healthindicators.HealthIndicatorsMod; +import me.andrew.healthindicators.HeartType; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.ShaderProgramKeys; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.render.*; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.render.entity.state.LivingEntityRenderState; +import net.minecraft.client.render.entity.state.PlayerEntityRenderState; +import net.minecraft.client.texture.GuiAtlasManager; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.scoreboard.ScoreboardDisplaySlot; +import net.minecraft.util.math.MathHelper; +import org.joml.Matrix4f; +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; + +@Mixin(LivingEntityRenderer.class) +public abstract class LivingEntityRenderMixin { + + @Shadow protected abstract boolean hasLabel(T livingEntity, double d); + + private static final MinecraftClient minecraftClient = MinecraftClient.getInstance(); + + @Inject( + method = "render(Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", + at = @At("TAIL") + ) + private void renderHearts(S livingEntityRenderState, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, CallbackInfo ci) { + if ( + livingEntityRenderState instanceof PlayerEntityRenderState playerEntityRenderState + && (Object) this instanceof PlayerEntityRenderer playerEntityRenderer + ) { + if (!Config.getRenderingEnabled()) return; + if (playerEntityRenderState.invisibleToPlayer) return; + if (minecraftClient.world == null) return; + + AbstractClientPlayerEntity player = null; + + for (AbstractClientPlayerEntity playerEntity : minecraftClient.world.getPlayers()) { + if (playerEntity.getName().getLiteralString().equals(playerEntityRenderState.name)) { + player = playerEntity; + break; + } + } + + if (player == null) return; + + matrixStack.push(); + + matrixStack.translate(0, playerEntityRenderState.height + 0.5f, 0); + if (this.hasLabel((T) player, playerEntityRenderState.squaredDistanceToCamera) && playerEntityRenderState.squaredDistanceToCamera <= 4096.0) { + matrixStack.translate(0.0D, 9.0F * 1.15F * 0.025F, 0.0D); + if (playerEntityRenderState.squaredDistanceToCamera < 100.0 && player.getScoreboard().getObjectiveForSlot(ScoreboardDisplaySlot.BELOW_NAME) != null) { + matrixStack.translate(0.0D, 9.0F * 1.15F * 0.025F, 0.0D); + } + } + + matrixStack.multiply(((EntityRendererAccessor) playerEntityRenderer).getDispatcher().getRotation()); + matrixStack.scale(-1, 1, 1); + + float pixelSize = 0.025F; + matrixStack.scale(pixelSize, pixelSize, pixelSize); + matrixStack.translate(0, Config.getHeartOffset(), 0); + + GuiAtlasManager guiAtlasManager = MinecraftClient.getInstance().getGuiAtlasManager(); + + RenderSystem.setShader(ShaderProgramKeys.POSITION_TEX); + RenderSystem.setShaderTexture(0, guiAtlasManager.getSprite(HeartType.EMPTY.texture).getAtlasId()); + RenderSystem.enableDepthTest(); + BufferBuilder vertexConsumer = Tessellator.getInstance().begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE); + + Matrix4f model = matrixStack.peek().getPositionMatrix(); + + int healthRed = MathHelper.ceil(player.getHealth()); + int maxHealth = MathHelper.ceil(player.getMaxHealth()); + int healthYellow = MathHelper.ceil(player.getAbsorptionAmount()); + + int heartsRed = MathHelper.ceil(healthRed / 2.0f); + boolean lastRedHalf = (healthRed & 1) == 1; + int heartsNormal = MathHelper.ceil(maxHealth / 2.0f); + int heartsYellow = MathHelper.ceil(healthYellow / 2.0f); + boolean lastYellowHalf = (healthYellow & 1) == 1; + int heartsTotal = heartsNormal + heartsYellow; + + int heartsPerRow = Config.getHeartStackingEnabled() ? 10 : heartsTotal; + int rowsTotal = (heartsTotal + heartsPerRow - 1) / heartsPerRow; + int rowOffset = Math.max(10 - (rowsTotal - 2), 3); + + int pixelsTotal = Math.min(heartsTotal, heartsPerRow) * 8 + 1; + float maxX = pixelsTotal / 2.0f; + for (int heart = 0; heart < heartsTotal; heart++){ + int row = heart / heartsPerRow; + int col = heart % heartsPerRow; + + float x = maxX - col * 8; + float y = row * rowOffset; + float z = row * 0.01F; + drawHeart(model, vertexConsumer, x, y, z, HeartType.EMPTY, guiAtlasManager); + + HeartType type; + if (heart < heartsRed) { + type = HeartType.RED_FULL; + if (heart == heartsRed - 1 && lastRedHalf) { + type = HeartType.RED_HALF; + } + } else if (heart < heartsNormal) { + type = HeartType.EMPTY; + } else { + type = HeartType.YELLOW_FULL; + if (heart == heartsTotal - 1 && lastYellowHalf) { + type = HeartType.YELLOW_HALF; + } + } + if (type != HeartType.EMPTY) { + drawHeart(model, vertexConsumer, x, y, z, type, guiAtlasManager); + } + } + + BufferRenderer.drawWithGlobalProgram(vertexConsumer.end()); + + matrixStack.pop(); + } + } + + @Unique + private static boolean shouldRenderHeartsForEntity(Entity entity) { + if (entity instanceof AbstractClientPlayerEntity abstractClientPlayerEntity) { + return !abstractClientPlayerEntity.isMainPlayer() && !abstractClientPlayerEntity.isInvisibleTo(MinecraftClient.getInstance().player); + } + + return false; + } + + @Unique + private static void drawHeart(Matrix4f model, VertexConsumer vertexConsumer, float x, float y, float z, HeartType type, GuiAtlasManager guiAtlasManager){ + Sprite sprite = guiAtlasManager.getSprite(type.texture); + + float minU = sprite.getMinU(); + float maxU = sprite.getMaxU(); + float minV = sprite.getMinV(); + float maxV = sprite.getMaxV(); + + float heartSize = 9F; + + drawVertex(model, vertexConsumer, x, y - heartSize, z, minU, maxV); + drawVertex(model, vertexConsumer, x - heartSize, y - heartSize, z, maxU, maxV); + drawVertex(model, vertexConsumer, x - heartSize, y, z, maxU, minV); + drawVertex(model, vertexConsumer, x, y, z, minU, minV); + } + + @Unique + private static void drawVertex(Matrix4f model, VertexConsumer vertices, float x, float y, float z, float u, float v) { + vertices.vertex(model, x, y, z).texture(u, v); + } +} diff --git a/src/main/java/me/andrew/healthindicators/mixin/PlayerEntityRendererMixin.java b/src/main/java/me/andrew/healthindicators/mixin/PlayerEntityRendererMixin.java deleted file mode 100644 index 4c50aa8..0000000 --- a/src/main/java/me/andrew/healthindicators/mixin/PlayerEntityRendererMixin.java +++ /dev/null @@ -1,150 +0,0 @@ -package me.andrew.healthindicators.mixin; - -import com.mojang.blaze3d.systems.RenderSystem; -import me.andrew.healthindicators.Config; -import me.andrew.healthindicators.HeartType; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.AbstractClientPlayerEntity; -import net.minecraft.client.render.*; -import net.minecraft.client.render.entity.EntityRendererFactory; -import net.minecraft.client.render.entity.LivingEntityRenderer; -import net.minecraft.client.render.entity.PlayerEntityRenderer; -import net.minecraft.client.render.entity.model.PlayerEntityModel; -import net.minecraft.client.texture.GuiAtlasManager; -import net.minecraft.client.texture.Sprite; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.entity.Entity; -import net.minecraft.scoreboard.ScoreboardDisplaySlot; -import net.minecraft.util.math.MathHelper; -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; - -@Mixin(PlayerEntityRenderer.class) -public abstract class PlayerEntityRendererMixin extends LivingEntityRenderer> { - public PlayerEntityRendererMixin(EntityRendererFactory.Context ctx, PlayerEntityModel model, float shadowRadius) { - super(ctx, model, shadowRadius); - } - - @Inject( - method = "render(Lnet/minecraft/client/network/AbstractClientPlayerEntity;FFLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;I)V", - at = @At("RETURN") - ) - public void renderHealth(AbstractClientPlayerEntity abstractClientPlayerEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, CallbackInfo ci) { - if (!Config.getRenderingEnabled()) return; - - if (!shouldRenderHeartsForEntity(abstractClientPlayerEntity)) return; - - matrixStack.push(); - - double d = this.dispatcher.getSquaredDistanceToCamera(abstractClientPlayerEntity); - - matrixStack.translate(0, abstractClientPlayerEntity.getHeight() + 0.5f, 0); - if (this.hasLabel(abstractClientPlayerEntity) && d <= 4096.0) { - matrixStack.translate(0.0D, 9.0F * 1.15F * 0.025F, 0.0D); - if (d < 100.0 && abstractClientPlayerEntity.getScoreboard().getObjectiveForSlot(ScoreboardDisplaySlot.BELOW_NAME) != null) { - matrixStack.translate(0.0D, 9.0F * 1.15F * 0.025F, 0.0D); - } - } - - matrixStack.multiply(this.dispatcher.getRotation()); -// matrixStack.multiply(Vector3f.POSITIVE_X.getDegreesQuaternion(mc.gameRenderer.getCamera().getPitch())); - matrixStack.scale(-1, 1, 1); - - float pixelSize = 0.025F; - matrixStack.scale(pixelSize, pixelSize, pixelSize); - matrixStack.translate(0, Config.getHeartOffset(), 0); - - GuiAtlasManager guiAtlasManager = MinecraftClient.getInstance().getGuiAtlasManager(); - - RenderSystem.setShader(GameRenderer::getPositionTexProgram); - RenderSystem.setShaderTexture(0, guiAtlasManager.getSprite(HeartType.EMPTY.texture).getAtlasId()); - RenderSystem.enableDepthTest(); - BufferBuilder vertexConsumer = Tessellator.getInstance().begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE); - - Matrix4f model = matrixStack.peek().getPositionMatrix(); - - int healthRed = MathHelper.ceil(abstractClientPlayerEntity.getHealth()); - int maxHealth = MathHelper.ceil(abstractClientPlayerEntity.getMaxHealth()); - int healthYellow = MathHelper.ceil(abstractClientPlayerEntity.getAbsorptionAmount()); - - int heartsRed = MathHelper.ceil(healthRed / 2.0f); - boolean lastRedHalf = (healthRed & 1) == 1; - int heartsNormal = MathHelper.ceil(maxHealth / 2.0f); - int heartsYellow = MathHelper.ceil(healthYellow / 2.0f); - boolean lastYellowHalf = (healthYellow & 1) == 1; - int heartsTotal = heartsNormal + heartsYellow; - - int heartsPerRow = Config.getHeartStackingEnabled() ? 10 : heartsTotal; - int rowsTotal = (heartsTotal + heartsPerRow - 1) / heartsPerRow; - int rowOffset = Math.max(10 - (rowsTotal - 2), 3); - - int pixelsTotal = Math.min(heartsTotal, heartsPerRow) * 8 + 1; - float maxX = pixelsTotal / 2.0f; - for (int heart = 0; heart < heartsTotal; heart++){ - int row = heart / heartsPerRow; - int col = heart % heartsPerRow; - - float x = maxX - col * 8; - float y = row * rowOffset; - float z = row * 0.01F; - drawHeart(model, vertexConsumer, x, y, z, HeartType.EMPTY, guiAtlasManager); - - HeartType type; - if (heart < heartsRed) { - type = HeartType.RED_FULL; - if (heart == heartsRed - 1 && lastRedHalf) { - type = HeartType.RED_HALF; - } - } else if (heart < heartsNormal) { - type = HeartType.EMPTY; - } else { - type = HeartType.YELLOW_FULL; - if (heart == heartsTotal - 1 && lastYellowHalf) { - type = HeartType.YELLOW_HALF; - } - } - if (type != HeartType.EMPTY) { - drawHeart(model, vertexConsumer, x, y, z, type, guiAtlasManager); - } - } - - BufferRenderer.drawWithGlobalProgram(vertexConsumer.end()); - - matrixStack.pop(); - } - - @Unique - private static boolean shouldRenderHeartsForEntity(Entity entity) { - if (entity instanceof AbstractClientPlayerEntity abstractClientPlayerEntity) { - return !abstractClientPlayerEntity.isMainPlayer() && !abstractClientPlayerEntity.isInvisibleTo(MinecraftClient.getInstance().player); - } - - return false; - } - - @Unique - private static void drawHeart(Matrix4f model, VertexConsumer vertexConsumer, float x, float y, float z, HeartType type, GuiAtlasManager guiAtlasManager){ - Sprite sprite = guiAtlasManager.getSprite(type.texture); - - float minU = sprite.getMinU(); - float maxU = sprite.getMaxU(); - float minV = sprite.getMinV(); - float maxV = sprite.getMaxV(); - - float heartSize = 9F; - - drawVertex(model, vertexConsumer, x, y - heartSize, z, minU, maxV); - drawVertex(model, vertexConsumer, x - heartSize, y - heartSize, z, maxU, maxV); - drawVertex(model, vertexConsumer, x - heartSize, y, z, maxU, minV); - drawVertex(model, vertexConsumer, x, y, z, minU, minV); - } - - @Unique - private static void drawVertex(Matrix4f model, VertexConsumer vertices, float x, float y, float z, float u, float v) { - vertices.vertex(model, x, y, z).texture(u, v); - } -} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 2cee043..5689d53 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -22,8 +22,8 @@ ], "depends": { - "fabricloader": ">=0.15.11", - "minecraft": "~1.21", + "fabricloader": ">=0.16.14", + "minecraft": ">=1.21.4", "java": ">=21", "fabric-api": "*" } diff --git a/src/main/resources/healthindicators.mixins.json b/src/main/resources/healthindicators.mixins.json index 69ad604..1065589 100644 --- a/src/main/resources/healthindicators.mixins.json +++ b/src/main/resources/healthindicators.mixins.json @@ -1,11 +1,12 @@ { - "required": true, - "package": "me.andrew.healthindicators.mixin", - "compatibilityLevel": "JAVA_21", - "client": [ - "PlayerEntityRendererMixin" - ], - "injectors": { - "defaultRequire": 1 + "required": true, + "package": "me.andrew.healthindicators.mixin", + "compatibilityLevel": "JAVA_21", + "client": [ + "EntityRendererAccessor", + "LivingEntityRenderMixin" + ], + "injectors": { + "defaultRequire": 1 } }