From 1c8d56f5aed9f7dec1005a1bdaa9bf8db373b458 Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 15 Dec 2023 23:56:35 -0700 Subject: [PATCH] Move hearts render over to Aria's Essentials --- src/main/disabledJavaFiles/HeartsRenderer.txt | 333 ----------------- .../zontreck/mcmods/WatchMyDurability.java | 4 +- .../mcmods/configs/WMDClientConfig.java | 1 - .../zontreck/mcmods/gui/HeartsRenderer.java | 334 ------------------ .../watchmydurability/textures/gui/absorb.png | Bin 6946 -> 0 bytes .../watchmydurability/textures/gui/hearts.png | Bin 5965 -> 0 bytes 6 files changed, 1 insertion(+), 671 deletions(-) delete mode 100644 src/main/disabledJavaFiles/HeartsRenderer.txt delete mode 100644 src/main/java/dev/zontreck/mcmods/gui/HeartsRenderer.java delete mode 100644 src/main/resources/assets/watchmydurability/textures/gui/absorb.png delete mode 100644 src/main/resources/assets/watchmydurability/textures/gui/hearts.png diff --git a/src/main/disabledJavaFiles/HeartsRenderer.txt b/src/main/disabledJavaFiles/HeartsRenderer.txt deleted file mode 100644 index 110c85f..0000000 --- a/src/main/disabledJavaFiles/HeartsRenderer.txt +++ /dev/null @@ -1,333 +0,0 @@ -/* - * - * DISCLAIMER: This code was taken from Mantle, and will be modified to fit the needs of this mod, such as adding more heat options. This code is subject to Mantle's license of MIT. - * Despite this code being taken from, and modified/updated to be modern, all textures are my own creation - * This disclaimer is here to give credit where credit is due. The author(s) of mantle have done a absolutely fantastic job. And if Mantle gets updated this shall be removed along with future plans of extra hearts and color options. - * - * - */ - -package dev.zontreck.mcmods.gui; - -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; - -import dev.zontreck.mcmods.WatchMyDurability; -import dev.zontreck.mcmods.configs.WMDClientConfig; -import net.minecraft.Util; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.effect.MobEffectInstance; -import net.minecraft.world.effect.MobEffects; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.ai.attributes.AttributeInstance; -import net.minecraft.world.entity.ai.attributes.Attributes; -import net.minecraft.world.entity.player.Player; -import net.minecraftforge.client.event.RenderGuiOverlayEvent; -import net.minecraftforge.client.gui.overlay.ForgeGui; -import net.minecraftforge.client.gui.overlay.GuiOverlayManager; -import net.minecraftforge.client.gui.overlay.NamedGuiOverlay; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.eventbus.api.EventPriority; -import net.minecraftforge.eventbus.api.SubscribeEvent; - -import java.util.Random; - -public class HeartsRenderer { - private static final ResourceLocation ICON_HEARTS = new ResourceLocation(WatchMyDurability.MODID, - "textures/gui/hearts.png"); - private static final ResourceLocation ICON_ABSORB = new ResourceLocation(WatchMyDurability.MODID, - "textures/gui/absorb.png"); - private static final ResourceLocation ICON_VANILLA = GuiComponent.GUI_ICONS_LOCATION; - - private final Minecraft mc = Minecraft.getInstance(); - - private int playerHealth = 0; - private int lastPlayerHealth = 0; - private long healthUpdateCounter = 0; - private long lastSystemTime = 0; - private final Random rand = new Random(); - - private int regen; - - /** - * Draws a texture to the screen - * - * @param matrixStack Matrix stack instance - * @param x X position - * @param y Y position - * @param textureX Texture X - * @param textureY Texture Y - * @param width Width to draw - * @param height Height to draw - */ - private void blit(PoseStack matrixStack, int x, int y, int textureX, int textureY, int width, int height) { - Minecraft.getInstance().gui.blit(matrixStack, x, y, textureX, textureY, width, height); - } - - /* HUD */ - - /** - * Event listener - * - * @param event Event instance - */ - @SubscribeEvent(priority = EventPriority.LOW) - public void renderHealthbar(RenderGuiOverlayEvent.Pre event) { - NamedGuiOverlay ActualOverlay = GuiOverlayManager.findOverlay(new ResourceLocation("minecraft:player_health")); - - if (ActualOverlay == null) { - if (GuiOverlayManager.getOverlays() == null) { - WatchMyDurability.LOGGER.info("Overlays non existent?!"); - } - for (NamedGuiOverlay overlay : GuiOverlayManager.getOverlays()) { - // Next print - // LibZontreck.LOGGER.info("GUI OVERLAY: "+overlay.id().getPath()); - - if (overlay.id().getPath().equals("player_health")) { - ActualOverlay = overlay; - break; - } - } - } - if (event.isCanceled() || !WMDClientConfig.EnableExtraHearts.get() || event.getOverlay() != ActualOverlay) { - return; - } - // ensure its visible - if (!(mc.gui instanceof ForgeGui gui) || mc.options.hideGui || !gui.shouldDrawSurvivalElements()) { - return; - } - Entity renderViewEnity = this.mc.getCameraEntity(); - if (!(renderViewEnity instanceof Player player)) { - return; - } - gui.setupOverlayRenderState(true, false); - - this.mc.getProfiler().push("health"); - - // extra setup stuff from us - int left_height = gui.leftHeight; - int width = this.mc.getWindow().getGuiScaledWidth(); - int height = this.mc.getWindow().getGuiScaledHeight(); - int updateCounter = this.mc.gui.getGuiTicks(); - - // start default forge/mc rendering - // changes are indicated by comment - - int health = Mth.ceil(player.getHealth()); - boolean highlight = this.healthUpdateCounter > (long) updateCounter - && (this.healthUpdateCounter - (long) updateCounter) / 3L % 2L == 1L; - - if (health < this.playerHealth && player.invulnerableTime > 0) { - this.lastSystemTime = Util.getMillis(); - this.healthUpdateCounter = (updateCounter + 20); - } else if (health > this.playerHealth && player.invulnerableTime > 0) { - this.lastSystemTime = Util.getMillis(); - this.healthUpdateCounter = (updateCounter + 10); - } - - if (Util.getMillis() - this.lastSystemTime > 1000L) { - this.playerHealth = health; - this.lastPlayerHealth = health; - this.lastSystemTime = Util.getMillis(); - } - - this.playerHealth = health; - int healthLast = this.lastPlayerHealth; - - AttributeInstance attrMaxHealth = player.getAttribute(Attributes.MAX_HEALTH); - float healthMax = attrMaxHealth == null ? 0 : (float) attrMaxHealth.getValue(); - float absorb = Mth.ceil(player.getAbsorptionAmount()); - - // CHANGE: simulate 10 hearts max if there's more, so vanilla only renders one - // row max - healthMax = Math.min(healthMax, 20f); - health = Math.min(health, 20); - absorb = Math.min(absorb, 20); - - int healthRows = Mth.ceil((healthMax + absorb) / 2.0F / 10.0F); - int rowHeight = Math.max(10 - (healthRows - 2), 3); - - this.rand.setSeed(updateCounter * 312871L); - - int left = width / 2 - 91; - int top = height - left_height; - // change: these are unused below, unneeded? should these adjust the Forge - // variable? - // left_height += (healthRows * rowHeight); - // if (rowHeight != 10) left_height += 10 - rowHeight; - - this.regen = -1; - if (player.hasEffect(MobEffects.REGENERATION)) { - this.regen = updateCounter % 25; - } - - assert this.mc.level != null; - final int TOP = 9 * (this.mc.level.getLevelData().isHardcore() ? 5 : 0); - final int BACKGROUND = (highlight ? 25 : 16); - int MARGIN = 16; - if (player.hasEffect(MobEffects.POISON)) - MARGIN += 36; - else if (player.hasEffect(MobEffects.WITHER)) - MARGIN += 72; - float absorbRemaining = absorb; - - PoseStack matrixStack = event.getPoseStack(); - for (int i = Mth.ceil((healthMax + absorb) / 2.0F) - 1; i >= 0; --i) { - int row = Mth.ceil((float) (i + 1) / 10.0F) - 1; - int x = left + i % 10 * 8; - int y = top - row * rowHeight; - - if (health <= 4) - y += this.rand.nextInt(2); - if (i == this.regen) - y -= 2; - - this.blit(matrixStack, x, y, BACKGROUND, TOP, 9, 9); - - if (highlight) { - if (i * 2 + 1 < healthLast) { - this.blit(matrixStack, x, y, MARGIN + 54, TOP, 9, 9); // 6 - } else if (i * 2 + 1 == healthLast) { - this.blit(matrixStack, x, y, MARGIN + 63, TOP, 9, 9); // 7 - } - } - - if (absorbRemaining > 0.0F) { - if (absorbRemaining == absorb && absorb % 2.0F == 1.0F) { - this.blit(matrixStack, x, y, MARGIN + 153, TOP, 9, 9); // 17 - absorbRemaining -= 1.0F; - } else { - this.blit(matrixStack, x, y, MARGIN + 144, TOP, 9, 9); // 16 - absorbRemaining -= 2.0F; - } - } else { - if (i * 2 + 1 < health) { - this.blit(matrixStack, x, y, MARGIN + 36, TOP, 9, 9); // 4 - } else if (i * 2 + 1 == health) { - this.blit(matrixStack, x, y, MARGIN + 45, TOP, 9, 9); // 5 - } - } - } - - this.renderExtraHearts(matrixStack, left, top, player); - this.renderExtraAbsorption(matrixStack, left, top - rowHeight, player); - - RenderSystem.setShaderTexture(0, ICON_VANILLA); - gui.leftHeight += 10; - if (absorb > 0) { - gui.leftHeight += 10; - } - - event.setCanceled(true); - RenderSystem.disableBlend(); - this.mc.getProfiler().pop(); - MinecraftForge.EVENT_BUS - .post(new RenderGuiOverlayEvent.Post(mc.getWindow(), matrixStack, event.getPartialTick(), ActualOverlay)); - } - - /** - * Gets the texture from potion effects - * - * @param player Player instance - * @return Texture offset for potion effects - */ - private int getPotionOffset(Player player) { - int potionOffset = 0; - MobEffectInstance potion = player.getEffect(MobEffects.WITHER); - if (potion != null) { - potionOffset = 18; - } - potion = player.getEffect(MobEffects.POISON); - if (potion != null) { - potionOffset = 9; - } - assert this.mc.level != null; - if (this.mc.level.getLevelData().isHardcore()) { - potionOffset += 27; - } - return potionOffset; - } - - /** - * Renders the health above 10 hearts - * - * @param matrixStack Matrix stack instance - * @param xBasePos Health bar top corner - * @param yBasePos Health bar top corner - * @param player Player instance - */ - private void renderExtraHearts(PoseStack matrixStack, int xBasePos, int yBasePos, Player player) { - int potionOffset = this.getPotionOffset(player); - - // Extra hearts - RenderSystem.setShaderTexture(0, ICON_HEARTS); - int hp = Mth.ceil(player.getHealth()); - this.renderCustomHearts(matrixStack, xBasePos, yBasePos, potionOffset, hp, false); - } - - /** - * Renders the absorption health above 10 hearts - * - * @param matrixStack Matrix stack instance - * @param xBasePos Health bar top corner - * @param yBasePos Health bar top corner - * @param player Player instance - */ - private void renderExtraAbsorption(PoseStack matrixStack, int xBasePos, int yBasePos, Player player) { - int potionOffset = this.getPotionOffset(player); - - // Extra hearts - RenderSystem.setShaderTexture(0, ICON_ABSORB); - int absorb = Mth.ceil(player.getAbsorptionAmount()); - this.renderCustomHearts(matrixStack, xBasePos, yBasePos, potionOffset, absorb, true); - } - - /** - * Gets the texture offset from the regen effect - * - * @param i Heart index - * @param offset Current offset - */ - private int getYRegenOffset(int i, int offset) { - return i + offset == this.regen ? -2 : 0; - } - - /** - * Shared logic to render custom hearts - * - * @param matrixStack Matrix stack instance - * @param xBasePos Health bar top corner - * @param yBasePos Health bar top corner - * @param potionOffset Offset from the potion effect - * @param count Number to render - * @param absorb If true, render absorption hearts - */ - private void renderCustomHearts(PoseStack matrixStack, int xBasePos, int yBasePos, int potionOffset, int count, - boolean absorb) { - int regenOffset = absorb ? 10 : 0; - for (int iter = 0; iter < count / 20; iter++) { - int renderHearts = (count - 20 * (iter + 1)) / 2; - int heartIndex = iter % 11; - if (renderHearts > 10) { - renderHearts = 10; - } - for (int i = 0; i < renderHearts; i++) { - int y = this.getYRegenOffset(i, regenOffset); - if (absorb) { - this.blit(matrixStack, xBasePos + 8 * i, yBasePos + y, 0, 54, 9, 9); - } - this.blit(matrixStack, xBasePos + 8 * i, yBasePos + y, 18 * heartIndex, potionOffset, 9, 9); - } - if (count % 2 == 1 && renderHearts < 10) { - int y = this.getYRegenOffset(renderHearts, regenOffset); - if (absorb) { - this.blit(matrixStack, xBasePos + 8 * renderHearts, yBasePos + y, 0, 54, 9, 9); - } - this.blit(matrixStack, xBasePos + 8 * renderHearts, yBasePos + y, 9 + 18 * heartIndex, potionOffset, 9, 9); - } - } - } -} diff --git a/src/main/java/dev/zontreck/mcmods/WatchMyDurability.java b/src/main/java/dev/zontreck/mcmods/WatchMyDurability.java index 8198f35..7026584 100644 --- a/src/main/java/dev/zontreck/mcmods/WatchMyDurability.java +++ b/src/main/java/dev/zontreck/mcmods/WatchMyDurability.java @@ -5,7 +5,6 @@ import com.mojang.logging.LogUtils; import dev.zontreck.ariaslib.util.DelayedExecutorService; import dev.zontreck.libzontreck.chat.ChatColor; import dev.zontreck.mcmods.configs.WMDClientConfig; -import dev.zontreck.mcmods.gui.HeartsRenderer; import net.minecraft.client.Minecraft; import net.minecraft.client.User; import net.minecraft.sounds.SoundEvent; @@ -55,8 +54,7 @@ public class WatchMyDurability // Register the commonSetup method for modloading modEventBus.addListener(this::commonSetup); ModLoadingContext.get().registerConfig(Type.CLIENT, WMDClientConfig.SPEC, "watchmydurability-client.toml"); - - MinecraftForge.EVENT_BUS.register(new HeartsRenderer()); + // Register ourselves for server and other game events we are interested in MinecraftForge.EVENT_BUS.register(this); } diff --git a/src/main/java/dev/zontreck/mcmods/configs/WMDClientConfig.java b/src/main/java/dev/zontreck/mcmods/configs/WMDClientConfig.java index d26912e..98a5210 100644 --- a/src/main/java/dev/zontreck/mcmods/configs/WMDClientConfig.java +++ b/src/main/java/dev/zontreck/mcmods/configs/WMDClientConfig.java @@ -34,7 +34,6 @@ public class WMDClientConfig { BUILDER.pop(); BUILDER.push("General"); - EnableExtraHearts = BUILDER.comment("Whether to enable the extra hearts rendering").define("compress_hearts", true); EnableHealthAlert = BUILDER.comment("The following was added for a friend. If you need reminders to eat in order to heal, turn the below option on").define("watchMyHealth", false); EnableHungerAlert = BUILDER.comment("This is a newer setting to watch your hunger status instead of your hunger to alert when you need to eat").define("watchMyHunger", true); diff --git a/src/main/java/dev/zontreck/mcmods/gui/HeartsRenderer.java b/src/main/java/dev/zontreck/mcmods/gui/HeartsRenderer.java deleted file mode 100644 index 2f76cae..0000000 --- a/src/main/java/dev/zontreck/mcmods/gui/HeartsRenderer.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * - * DISCLAIMER: This code was taken from Mantle, and will be modified to fit the needs of this mod, such as adding more heat options. This code is subject to Mantle's license of MIT. - * Despite this code being taken from, and modified/updated to be modern, all textures are my own creation - * This disclaimer is here to give credit where credit is due. The author(s) of mantle have done a absolutely fantastic job. And if Mantle gets updated this shall be removed along with future plans of extra hearts and color options. - * - * - */ - -package dev.zontreck.mcmods.gui; - -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; - -import dev.zontreck.mcmods.WatchMyDurability; -import dev.zontreck.mcmods.configs.WMDClientConfig; -import net.minecraft.Util; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Gui; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.effect.MobEffectInstance; -import net.minecraft.world.effect.MobEffects; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.ai.attributes.AttributeInstance; -import net.minecraft.world.entity.ai.attributes.Attributes; -import net.minecraft.world.entity.player.Player; -import net.minecraftforge.client.event.RenderGuiOverlayEvent; -import net.minecraftforge.client.gui.overlay.ForgeGui; -import net.minecraftforge.client.gui.overlay.GuiOverlayManager; -import net.minecraftforge.client.gui.overlay.NamedGuiOverlay; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.eventbus.api.EventPriority; -import net.minecraftforge.eventbus.api.SubscribeEvent; - -import java.util.Random; - -public class HeartsRenderer { - private static final ResourceLocation ICON_HEARTS = new ResourceLocation(WatchMyDurability.MODID, - "textures/gui/hearts.png"); - private static final ResourceLocation ICON_ABSORB = new ResourceLocation(WatchMyDurability.MODID, - "textures/gui/absorb.png"); - private static final ResourceLocation ICON_VANILLA = Gui.GUI_ICONS_LOCATION; - - private final Minecraft mc = Minecraft.getInstance(); - - private int playerHealth = 0; - private int lastPlayerHealth = 0; - private long healthUpdateCounter = 0; - private long lastSystemTime = 0; - private final Random rand = new Random(); - - private int regen; - - /** - * Draws a texture to the screen - * - * @param matrixStack Matrix stack instance - * @param x X position - * @param y Y position - * @param textureX Texture X - * @param textureY Texture Y - * @param width Width to draw - * @param height Height to draw - */ - private void blit(GuiGraphics matrixStack, int x, int y, int textureX, int textureY, int width, int height, ResourceLocation resource) { - matrixStack.blit(resource, x, y, textureX, textureY, width, height); - } - - /* HUD */ - - /** - * Event listener - * - * @param event Event instance - */ - @SubscribeEvent(priority = EventPriority.LOW) - public void renderHealthbar(RenderGuiOverlayEvent.Pre event) { - NamedGuiOverlay ActualOverlay = GuiOverlayManager.findOverlay(new ResourceLocation("minecraft:player_health")); - - if (ActualOverlay == null) { - if (GuiOverlayManager.getOverlays() == null) { - WatchMyDurability.LOGGER.info("Overlays non existent?!"); - } - for (NamedGuiOverlay overlay : GuiOverlayManager.getOverlays()) { - // Next print - // LibZontreck.LOGGER.info("GUI OVERLAY: "+overlay.id().getPath()); - - if (overlay.id().getPath().equals("player_health")) { - ActualOverlay = overlay; - break; - } - } - } - if (event.isCanceled() || !WMDClientConfig.EnableExtraHearts.get() || event.getOverlay() != ActualOverlay) { - return; - } - // ensure its visible - if (!(mc.gui instanceof ForgeGui gui) || mc.options.hideGui || !gui.shouldDrawSurvivalElements()) { - return; - } - Entity renderViewEnity = this.mc.getCameraEntity(); - if (!(renderViewEnity instanceof Player player)) { - return; - } - gui.setupOverlayRenderState(true, false); - - this.mc.getProfiler().push("health"); - - // extra setup stuff from us - int left_height = gui.leftHeight; - int width = this.mc.getWindow().getGuiScaledWidth(); - int height = this.mc.getWindow().getGuiScaledHeight(); - int updateCounter = this.mc.gui.getGuiTicks(); - - // start default forge/mc rendering - // changes are indicated by comment - - int health = Mth.ceil(player.getHealth()); - boolean highlight = this.healthUpdateCounter > (long) updateCounter - && (this.healthUpdateCounter - (long) updateCounter) / 3L % 2L == 1L; - - if (health < this.playerHealth && player.invulnerableTime > 0) { - this.lastSystemTime = Util.getMillis(); - this.healthUpdateCounter = (updateCounter + 20); - } else if (health > this.playerHealth && player.invulnerableTime > 0) { - this.lastSystemTime = Util.getMillis(); - this.healthUpdateCounter = (updateCounter + 10); - } - - if (Util.getMillis() - this.lastSystemTime > 1000L) { - this.playerHealth = health; - this.lastPlayerHealth = health; - this.lastSystemTime = Util.getMillis(); - } - - this.playerHealth = health; - int healthLast = this.lastPlayerHealth; - - AttributeInstance attrMaxHealth = player.getAttribute(Attributes.MAX_HEALTH); - float healthMax = attrMaxHealth == null ? 0 : (float) attrMaxHealth.getValue(); - float absorb = Mth.ceil(player.getAbsorptionAmount()); - - // CHANGE: simulate 10 hearts max if there's more, so vanilla only renders one - // row max - healthMax = Math.min(healthMax, 20f); - health = Math.min(health, 20); - absorb = Math.min(absorb, 20); - - int healthRows = Mth.ceil((healthMax + absorb) / 2.0F / 10.0F); - int rowHeight = Math.max(10 - (healthRows - 2), 3); - - this.rand.setSeed(updateCounter * 312871L); - - int left = width / 2 - 91; - int top = height - left_height; - // change: these are unused below, unneeded? should these adjust the Forge - // variable? - // left_height += (healthRows * rowHeight); - // if (rowHeight != 10) left_height += 10 - rowHeight; - - this.regen = -1; - if (player.hasEffect(MobEffects.REGENERATION)) { - this.regen = updateCounter % 25; - } - - assert this.mc.level != null; - final int TOP = 9 * (this.mc.level.getLevelData().isHardcore() ? 5 : 0); - final int BACKGROUND = (highlight ? 25 : 16); - int MARGIN = 16; - if (player.hasEffect(MobEffects.POISON)) - MARGIN += 36; - else if (player.hasEffect(MobEffects.WITHER)) - MARGIN += 72; - float absorbRemaining = absorb; - - GuiGraphics matrixStack = event.getGuiGraphics(); - for (int i = Mth.ceil((healthMax + absorb) / 2.0F) - 1; i >= 0; --i) { - int row = Mth.ceil((float) (i + 1) / 10.0F) - 1; - int x = left + i % 10 * 8; - int y = top - row * rowHeight; - - if (health <= 4) - y += this.rand.nextInt(2); - if (i == this.regen) - y -= 2; - - this.blit(matrixStack, x, y, BACKGROUND, TOP, 9, 9, ICON_VANILLA); - - if (highlight) { - if (i * 2 + 1 < healthLast) { - this.blit(matrixStack, x, y, MARGIN + 54, TOP, 9, 9, ICON_VANILLA); // 6 - } else if (i * 2 + 1 == healthLast) { - this.blit(matrixStack, x, y, MARGIN + 63, TOP, 9, 9, ICON_VANILLA); // 7 - } - } - - if (absorbRemaining > 0.0F) { - if (absorbRemaining == absorb && absorb % 2.0F == 1.0F) { - this.blit(matrixStack, x, y, MARGIN + 153, TOP, 9, 9, ICON_VANILLA); // 17 - absorbRemaining -= 1.0F; - } else { - this.blit(matrixStack, x, y, MARGIN + 144, TOP, 9, 9, ICON_VANILLA); // 16 - absorbRemaining -= 2.0F; - } - } else { - if (i * 2 + 1 < health) { - this.blit(matrixStack, x, y, MARGIN + 36, TOP, 9, 9, ICON_VANILLA); // 4 - } else if (i * 2 + 1 == health) { - this.blit(matrixStack, x, y, MARGIN + 45, TOP, 9, 9, ICON_VANILLA); // 5 - } - } - } - - this.renderExtraHearts(matrixStack, left, top, player); - this.renderExtraAbsorption(matrixStack, left, top - rowHeight, player); - - RenderSystem.setShaderTexture(0, ICON_VANILLA); - gui.leftHeight += 10; - if (absorb > 0) { - gui.leftHeight += 10; - } - - event.setCanceled(true); - RenderSystem.disableBlend(); - this.mc.getProfiler().pop(); - MinecraftForge.EVENT_BUS - .post(new RenderGuiOverlayEvent.Post(mc.getWindow(), event.getGuiGraphics(), event.getPartialTick(), ActualOverlay)); - } - - /** - * Gets the texture from potion effects - * - * @param player Player instance - * @return Texture offset for potion effects - */ - private int getPotionOffset(Player player) { - int potionOffset = 0; - MobEffectInstance potion = player.getEffect(MobEffects.WITHER); - if (potion != null) { - potionOffset = 18; - } - potion = player.getEffect(MobEffects.POISON); - if (potion != null) { - potionOffset = 9; - } - assert this.mc.level != null; - if (this.mc.level.getLevelData().isHardcore()) { - potionOffset += 27; - } - return potionOffset; - } - - /** - * Renders the health above 10 hearts - * - * @param matrixStack Matrix stack instance - * @param xBasePos Health bar top corner - * @param yBasePos Health bar top corner - * @param player Player instance - */ - private void renderExtraHearts(GuiGraphics matrixStack, int xBasePos, int yBasePos, Player player) { - int potionOffset = this.getPotionOffset(player); - - // Extra hearts - RenderSystem.setShaderTexture(0, ICON_HEARTS); - int hp = Mth.ceil(player.getHealth()); - this.renderCustomHearts(matrixStack, xBasePos, yBasePos, potionOffset, hp, false); - } - - /** - * Renders the absorption health above 10 hearts - * - * @param matrixStack Matrix stack instance - * @param xBasePos Health bar top corner - * @param yBasePos Health bar top corner - * @param player Player instance - */ - private void renderExtraAbsorption(GuiGraphics matrixStack, int xBasePos, int yBasePos, Player player) { - int potionOffset = this.getPotionOffset(player); - - // Extra hearts - RenderSystem.setShaderTexture(0, ICON_ABSORB); - int absorb = Mth.ceil(player.getAbsorptionAmount()); - this.renderCustomHearts(matrixStack, xBasePos, yBasePos, potionOffset, absorb, true); - } - - /** - * Gets the texture offset from the regen effect - * - * @param i Heart index - * @param offset Current offset - */ - private int getYRegenOffset(int i, int offset) { - return i + offset == this.regen ? -2 : 0; - } - - /** - * Shared logic to render custom hearts - * - * @param matrixStack Matrix stack instance - * @param xBasePos Health bar top corner - * @param yBasePos Health bar top corner - * @param potionOffset Offset from the potion effect - * @param count Number to render - * @param absorb If true, render absorption hearts - */ - private void renderCustomHearts(GuiGraphics matrixStack, int xBasePos, int yBasePos, int potionOffset, int count, - boolean absorb) { - int regenOffset = absorb ? 10 : 0; - for (int iter = 0; iter < count / 20; iter++) { - int renderHearts = (count - 20 * (iter + 1)) / 2; - int heartIndex = iter % 11; - if (renderHearts > 10) { - renderHearts = 10; - } - for (int i = 0; i < renderHearts; i++) { - int y = this.getYRegenOffset(i, regenOffset); - if (absorb) { - this.blit(matrixStack, xBasePos + 8 * i, yBasePos + y, 0, 54, 9, 9, ICON_ABSORB); - } - this.blit(matrixStack, xBasePos + 8 * i, yBasePos + y, 18 * heartIndex, potionOffset, 9, 9, ICON_HEARTS); - } - if (count % 2 == 1 && renderHearts < 10) { - int y = this.getYRegenOffset(renderHearts, regenOffset); - if (absorb) { - this.blit(matrixStack, xBasePos + 8 * renderHearts, yBasePos + y, 0, 54, 9, 9, ICON_ABSORB); - } - this.blit(matrixStack, xBasePos + 8 * renderHearts, yBasePos + y, 9 + 18 * heartIndex, potionOffset, 9, 9, ICON_HEARTS); - } - } - } -} diff --git a/src/main/resources/assets/watchmydurability/textures/gui/absorb.png b/src/main/resources/assets/watchmydurability/textures/gui/absorb.png deleted file mode 100644 index 37d83b505fb64501fc6389244a9148186c9e5137..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6946 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 SHU<7a4$-}C058