package systems.brn.hudbars.mixin; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.hud.InGameHud; import net.minecraft.client.render.RenderTickCounter; import net.minecraft.entity.JumpingMount; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.effect.StatusEffects; import net.minecraft.entity.player.HungerManager; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.registry.tag.FluidTags; import net.minecraft.util.Util; import net.minecraft.util.math.MathHelper; import net.minecraft.world.GameMode; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import static net.minecraft.entity.effect.StatusEffects.POISON; import static net.minecraft.entity.effect.StatusEffects.WITHER; @Mixin(InGameHud.class) public class InGameHudMixin { @Inject(method = "render", at = @At("HEAD"), cancellable = true) private void renderMainHud(DrawContext context, RenderTickCounter tickCounter, CallbackInfo ci) { InGameHud hud = (InGameHud) (Object) this; MinecraftClient client = hud.client; PlayerEntity player = client.player; if (client.interactionManager.getCurrentGameMode() == GameMode.SPECTATOR) { hud.getSpectatorHud().renderSpectatorMenu(context); } else { hud.renderHotbar(context, tickCounter); } if (player == null) return; // Calculate HUD bar positions int centerX = context.getScaledWindowWidth() / 2 - 91; // Render Jump Bar for Mount JumpingMount jumpingMount = client.player.getJumpingMount(); if (jumpingMount != null) { renderMountJumpBar(jumpingMount, context, centerX); } // Render Status Bars (Health, Armor, Food, Air) renderStatusBars(context); // Render Mount Health (if applicable) renderMountHealth(context); // Render Experience Bar renderExperienceBar(context, centerX); // Render Held Item Tooltip if (client.interactionManager.getCurrentGameMode() != GameMode.SPECTATOR) { hud.renderHeldItemTooltip(context); } else if (player.isSpectator()) { hud.getSpectatorHud().render(context); } hud.renderCrosshair(context, tickCounter); ci.cancel(); } private void renderStatusBars(DrawContext context) { InGameHud hud = (InGameHud) (Object) this; MinecraftClient client = hud.client; PlayerEntity player = hud.getCameraPlayer(); if (player != null) { int lastHealth = MathHelper.ceil(player.getHealth()); boolean bl = hud.heartJumpEndTick > (long) hud.getTicks() && (hud.heartJumpEndTick - (long) hud.getTicks()) / 3L % 2L == 1L; int screenWidth = context.getScaledWindowWidth(); int screenHeight = context.getScaledWindowHeight(); int baseY = screenHeight - 39; long l = Util.getMeasuringTimeMs(); if (lastHealth < hud.lastHealthValue && player.timeUntilRegen > 0) { hud.lastHealthCheckTime = l; hud.heartJumpEndTick = (long) (hud.getTicks() + 20); } else if (lastHealth > hud.lastHealthValue && player.timeUntilRegen > 0) { hud.lastHealthCheckTime = l; hud.heartJumpEndTick = (long) (hud.getTicks() + 10); } if (l - hud.lastHealthCheckTime > 1000L) { hud.lastHealthValue = lastHealth; hud.renderHealthValue = lastHealth; hud.lastHealthCheckTime = l; } hud.lastHealthValue = lastHealth; int health = hud.renderHealthValue; int x = context.getScaledWindowWidth() / 2 - 91; int m = context.getScaledWindowWidth() / 2 + 91; int y = context.getScaledWindowHeight() - 39; float maxHealthFloat = Math.max((float) player.getAttributeValue(EntityAttributes.MAX_HEALTH), (float) Math.max(health, lastHealth)); int absorption = MathHelper.ceil(player.getAbsorptionAmount()); int p = MathHelper.ceil((maxHealthFloat + (float) absorption) / 2.0F / 10.0F); int q = Math.max(10 - (p - 2), 3); renderArmor(context, player, y, p, q, x); renderHealthBar(context, player, x, y, maxHealthFloat, lastHealth, health, absorption, bl); renderFood(context, player, y, m); renderAir(context, player, baseY, screenWidth / 2 + 91); } } private void renderMountHealth(DrawContext context) { InGameHud hud = (InGameHud) (Object) this; LivingEntity mount = hud.getRiddenEntity(); if (mount != null) { int health = (int) Math.ceil(mount.getHealth()); int screenHeight = context.getScaledWindowHeight(); int screenWidth = context.getScaledWindowWidth(); int baseX = screenWidth / 2 + 12; int baseY = screenHeight - 52; renderHealthBar(context, mount, baseX, baseY, mount.getMaxHealth(), health, health, 0, false); } } private void renderHealthBar(DrawContext context, LivingEntity player, int x, int y, float maxHealthFloat, int lastHealth, int health, int absorption, boolean blinking) { int barWidth = 80; int barHeight = 10; int maxHealth = (int) maxHealthFloat; y -= 8; int color; int borderColor; if (blinking && lastHealth != health) { borderColor = 0xFFFFFFFF; } else if (blinking) { borderColor = 0xFFFF0000; } else if (lastHealth != health) { borderColor = 0xFFFF80FF; } else { borderColor = 0xFF000000; } if (player.hasStatusEffect(POISON)) { color = 0xFF004400; } else if (player.hasStatusEffect(WITHER)) { color = 0xFF000000; } else if (player.isFrozen()) { color = 0xFF00FFFF; } else if (absorption > 0) { color = 0xFFAAAA00; } else { color = 0xFF55AA00; } float healthPercentage = (float) lastHealth / maxHealth; String healthText = "%d/%d".formatted(lastHealth + absorption, maxHealth); drawBar(context, x, y, barWidth, barHeight, healthPercentage, color, borderColor, healthText, 0, 0); } @Unique private static int getArmorToughness(LivingEntity livingEntity) { return MathHelper.floor(livingEntity.getAttributeValue(EntityAttributes.ARMOR_TOUGHNESS)); } @Unique private static int computeArmorColor(float armorPercentage) { // Gradually interpolate between grey (low armor) and green (high armor) int red = (int) (0xAA * (1.0f - armorPercentage)); // Red decreases as armor increases int green = (int) (0xFF * armorPercentage); // Green increases as armor increases int blue = (int) (0x55 + (0xAA * armorPercentage)); // Blue shifts from grey to green // Combine the RGB values into a single color integer return (0xFF << 24) | (red << 16) | (green << 8) | blue; } @Unique private static void renderArmor(DrawContext context, PlayerEntity player, int i, int j, int k, int x) { int armorLevel = player.getArmor(); int armorToughness = getArmorToughness(player); // Skip rendering if armor level is 0 if (armorLevel > 0) { int barWidth = 80; int barHeight = 10; int posY = i - (j - 1) * k - 21; // Compute the color based on the armor level percentage float armorPercentage = (float) armorLevel / 20.0f; float toughnessPercentage = (float) armorToughness / 20.0f; int color = computeArmorColor(armorPercentage); // Armor text to display String armorText = "%d/%d".formatted(armorLevel, 20); // Use the drawBar method to render the armor bar drawBar(context, x, posY, barWidth, barHeight, armorPercentage, color, 0xFF000000, armorText, toughnessPercentage, generateSubBarColor(color)); RenderSystem.disableBlend(); } } private void renderMountJumpBar(JumpingMount mount, DrawContext context, int x) { InGameHud hud = (InGameHud) (Object) this; MinecraftClient client = hud.client; int screenHeight = context.getScaledWindowHeight(); int y = screenHeight - 68; float jumpStrength = client.player.getMountJumpStrength(); // Background color for the bar int backgroundColor = 0xFF555555; // Grey background // Main bar color int barColor = 0xFF00FF00; // Green color for the jump bar (can be adjusted) // Border color int borderColor = 0xFFFFFFFF; // White border // Text to display String text = ""; // Assuming no text to be displayed on the jump bar // Inner bar color, if applicable int innerBarColor = 0xFFFF0000; // Red color for the inner bar (can be adjusted) // Calculate fill percentage based on jump strength float fillPercentage = jumpStrength; // Check for cooldown float innerPercentage = (mount.getJumpCooldown() > 0) ? 1.0f : 0.0f; // Draw the bar using the drawBar function drawBar(context, x, y, 182, 5, fillPercentage, barColor, borderColor, text, innerPercentage, innerBarColor); } private void renderExperienceBar(DrawContext context, int x) { InGameHud hud = (InGameHud) (Object) this; MinecraftClient client = hud.client; PlayerEntity player = client.player; if (player == null) return; int screenHeight = context.getScaledWindowHeight(); int experienceNeededForNextLevel = player.getNextLevelExperience(); if (experienceNeededForNextLevel > 0) { int experienceGotForThisLevel = (int) (player.experienceProgress * experienceNeededForNextLevel); int experienceLevel = player.experienceLevel; int barWidth = 183; int barHeight = 10; int filledWidth = (int) (player.experienceProgress * barWidth); int y = screenHeight - 34; RenderSystem.enableBlend(); // Display experienceGotForThisLevel/experienceNeededForNextLevel String experienceText = "%d/%d (%d)".formatted(experienceGotForThisLevel, experienceNeededForNextLevel, experienceLevel); drawBar(context, x, y, barWidth, barHeight, (float) filledWidth / barWidth, 0xFFAAAA00, 0xFF000000, experienceText, 0, 0); RenderSystem.disableBlend(); } } private void renderFood(DrawContext context, PlayerEntity player, int top, int right) { HungerManager hungerManager = player.getHungerManager(); int foodLevel = hungerManager.getFoodLevel(); float saturationLevel = hungerManager.getSaturationLevel(); int barWidth = 80; int barHeight = 10; int x = right - barWidth + 1; int y = top - 8; int color; if (player.hasStatusEffect(StatusEffects.HUNGER)) { color = 0xFFAA55AA; } else if (player.hasStatusEffect(StatusEffects.SATURATION)) { color = 0xFF00FF00; } else if (foodLevel >= 20) { color = 0xFF00FF00; } else if (foodLevel >= 18) { color = 0xFF33FF00; } else if (foodLevel > 6) { color = 0xFF66FF00; } else if (foodLevel > 0) { color = 0xFFFFFF00; } else { color = 0xFFFF0000; } float foodPercentage = (float) foodLevel / 20.0f; String foodText = "%d(%d)/%d".formatted(foodLevel, (int) saturationLevel, 20); // Calculate the saturation percentage relative to the current food level float saturationPercentage = saturationLevel / 20.0f; // Use the drawBar method with the innerPercentage and innerColor for saturation drawBar(context, x, y, barWidth, barHeight, foodPercentage, color, 0xFF000000, foodText, saturationPercentage, generateSubBarColor(color)); } private void renderAir(DrawContext context, PlayerEntity player, int top, int right) { int air = player.getAir(); int maxAir = player.getMaxAir(); int barWidth = 80; int barHeight = 10; int x = right - barWidth + 1; int y = top - 22; if (air < maxAir || player.isSubmergedIn(FluidTags.WATER)) { float airPercentage = (float) air / (float) maxAir; if (airPercentage < 0) { airPercentage = 0; } String airText = "%d/%d".formatted(air, maxAir); drawBar(context, x, y, barWidth, barHeight, airPercentage, 0xFF00FFFF, 0xFF000000, airText, 0, 0); } } @Unique private static void drawBar(DrawContext context, int x, int y, int barWidth, int barHeight, float percentage, int color, int borderColor, String text, float innerPercentage, int innerColor) { // Draw outer border context.fill(x - 1, y - 1, x + barWidth + 1, y + barHeight + 1, borderColor); // Draw background context.fill(x, y, x + barWidth, y + barHeight, 0xFF555555); // Grey background // Calculate the filled width based on the percentage int filledWidth = (int) (barWidth * percentage); // Draw the filled part of the bar context.fill(x, y, x + filledWidth, y + barHeight, color); // Draw the inner bar if applicable if (innerPercentage > 0) { int innerFilledWidth = (int) (barWidth * innerPercentage); context.fill(x, y, x + innerFilledWidth, y + barHeight, innerColor); } // Render the text TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; context.drawText(textRenderer, text, x + 2, y + 1, 0xFFFFFFFF, false); } @Unique private static int generateSubBarColor(int color) { // Mask the alpha channel (first byte) int alpha = color & 0xFF000000; // Extract RGB channels int red = (color & 0x00FF0000) >> 16; int green = (color & 0x0000FF00) >> 8; int blue = color & 0x000000FF; // Halve each RGB channel red = Math.max(red / 2, 0); green = Math.max(green / 2, 0); blue = Math.max(blue / 2, 0); // Combine the halved RGB channels back into the color int RGB = (red << 16) | (green << 8) | blue; // Combine the alpha channel with the modified RGB channels return alpha | RGB; } }