From 78e2ed41adf03c4d8d43b81da250fac4db56eab0 Mon Sep 17 00:00:00 2001 From: Waterpicker Date: Fri, 30 Jan 2026 01:50:08 -0600 Subject: [PATCH 01/11] tabs --- .../appleskin/client/HUDOverlayHandler.java | 1132 ++++++++--------- 1 file changed, 566 insertions(+), 566 deletions(-) diff --git a/java/squeek/appleskin/client/HUDOverlayHandler.java b/java/squeek/appleskin/client/HUDOverlayHandler.java index 99385df..681189b 100644 --- a/java/squeek/appleskin/client/HUDOverlayHandler.java +++ b/java/squeek/appleskin/client/HUDOverlayHandler.java @@ -34,570 +34,570 @@ @OnlyIn(Dist.CLIENT) public class HUDOverlayHandler { - private static float unclampedFlashAlpha = 0f; - private static float flashAlpha = 0f; - private static byte alphaDir = 1; - protected static int foodIconsOffset; - - public static final Vector healthBarOffsets = new Vector<>(); - public static final Vector foodBarOffsets = new Vector<>(); - - private static final Random random = new Random(); - - public static void init() - { - MinecraftForge.EVENT_BUS.register(new HUDOverlayHandler()); - } - - static ResourceLocation FOOD_LEVEL_ELEMENT = new ResourceLocation("minecraft", "food_level"); - static ResourceLocation PLAYER_HEALTH_ELEMENT = new ResourceLocation("minecraft", "player_health"); - - @SubscribeEvent - public void onRenderGuiOverlayPre(RenderGuiOverlayEvent.Pre event) - { - if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) - { - Minecraft mc = Minecraft.getInstance(); - ForgeGui gui = (ForgeGui) mc.gui; - boolean isMounted = mc.player.getVehicle() instanceof LivingEntity; - if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) - { - renderExhaustion(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight()); - } - } - } - - @SubscribeEvent - public void onRenderGuiOverlayPost(RenderGuiOverlayEvent.Post event) - { - if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) - { - Minecraft mc = Minecraft.getInstance(); - ForgeGui gui = (ForgeGui) mc.gui; - boolean isMounted = mc.player.getVehicle() instanceof LivingEntity; - if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) - { - renderFoodOrHealthOverlay(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight(), RenderOverlayType.FOOD); - } - } - else if (event.getOverlay() == GuiOverlayManager.findOverlay(PLAYER_HEALTH_ELEMENT)) - { - Minecraft mc = Minecraft.getInstance(); - ForgeGui gui = (ForgeGui) mc.gui; - if (!mc.options.hideGui && gui.shouldDrawSurvivalElements()) - { - renderFoodOrHealthOverlay(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight(), RenderOverlayType.HEALTH); - } - } - } - - public static void renderExhaustion(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight) - { - foodIconsOffset = gui.rightHeight; - - if (!ModConfig.SHOW_FOOD_EXHAUSTION_UNDERLAY.get()) - return; - - Minecraft mc = Minecraft.getInstance(); - Player player = mc.player; - assert player != null; - - int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; - int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; - float exhaustion = player.getFoodData().getExhaustionLevel(); - - // Notify everyone that we should render exhaustion hud overlay - HUDOverlayEvent.Exhaustion renderEvent = new HUDOverlayEvent.Exhaustion(exhaustion, right, top, guiGraphics); - MinecraftForge.EVENT_BUS.post(renderEvent); - if (!renderEvent.isCanceled()) - drawExhaustionOverlay(renderEvent, mc, 1f); - } - - enum RenderOverlayType - { - HEALTH, - FOOD, - } - - public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight, RenderOverlayType type) - { - if (!shouldRenderAnyOverlays()) - return; - - Minecraft mc = Minecraft.getInstance(); - Player player = mc.player; - assert player != null; - FoodData stats = player.getFoodData(); - - int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; - int left = mc.getWindow().getGuiScaledWidth() / 2 - 91; // left of health bar - int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; // right of food bar - - if (type == RenderOverlayType.HEALTH) - generateHealthBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); - if (type == RenderOverlayType.FOOD) - generateHungerBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); - - HUDOverlayEvent.Saturation saturationRenderEvent = null; - if (type == RenderOverlayType.FOOD) - { - saturationRenderEvent = new HUDOverlayEvent.Saturation(stats.getSaturationLevel(), right, top, guiGraphics); - - // cancel render overlay event when configuration disabled. - if (!ModConfig.SHOW_SATURATION_OVERLAY.get()) - saturationRenderEvent.setCanceled(true); - - // notify everyone that we should render saturation hud overlay - if (!saturationRenderEvent.isCanceled()) - MinecraftForge.EVENT_BUS.post(saturationRenderEvent); - - // the render saturation event maybe cancelled by other mods - if (!saturationRenderEvent.isCanceled()) - drawSaturationOverlay(saturationRenderEvent, mc, 0, 1f); - } - - // try to get the item stack in the player hand - ItemStack heldItem = player.getMainHandItem(); - if (ModConfig.SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND.get() && !FoodHelper.canConsume(heldItem, player)) - heldItem = player.getOffhandItem(); - - boolean shouldRenderHeldItemValues = !heldItem.isEmpty() && FoodHelper.canConsume(heldItem, player); - if (!shouldRenderHeldItemValues) - { - resetFlash(); - return; - } - - FoodValues modifiedFoodValues = FoodHelper.getModifiedFoodValues(heldItem, player); - FoodValuesEvent foodValuesEvent = new FoodValuesEvent(player, heldItem, FoodHelper.getDefaultFoodValues(heldItem, player), modifiedFoodValues); - MinecraftForge.EVENT_BUS.post(foodValuesEvent); - modifiedFoodValues = foodValuesEvent.modifiedFoodValues; - - if (type == RenderOverlayType.HEALTH) - { - // Offsets size is set to zero intentionally to disable rendering when health is infinite. - if (healthBarOffsets.size() == 0) - return; - - if (!shouldShowEstimatedHealth(heldItem, modifiedFoodValues)) - return; - - float foodHealthIncrement = FoodHelper.getEstimatedHealthIncrement(heldItem, modifiedFoodValues, player); - float currentHealth = player.getHealth(); - float modifiedHealth = Math.min(currentHealth + foodHealthIncrement, player.getMaxHealth()); - - // only create object when the estimated health is successfully - HUDOverlayEvent.HealthRestored healthRenderEvent = null; - if (currentHealth < modifiedHealth) - healthRenderEvent = new HUDOverlayEvent.HealthRestored(modifiedHealth, heldItem, modifiedFoodValues, left, top, guiGraphics); - - // notify everyone that we should render estimated health hud - if (healthRenderEvent != null) - MinecraftForge.EVENT_BUS.post(healthRenderEvent); - - if (healthRenderEvent != null && !healthRenderEvent.isCanceled()) - drawHealthOverlay(healthRenderEvent, mc, flashAlpha); - } - else if (type == RenderOverlayType.FOOD) - { - if (!ModConfig.SHOW_FOOD_VALUES_OVERLAY.get()) - return; - - // notify everyone that we should render hunger hud overlay - HUDOverlayEvent.HungerRestored renderRenderEvent = new HUDOverlayEvent.HungerRestored(stats.getFoodLevel(), heldItem, modifiedFoodValues, right, top, guiGraphics); - MinecraftForge.EVENT_BUS.post(renderRenderEvent); - if (renderRenderEvent.isCanceled()) - return; - - // calculate the final hunger and saturation - int foodHunger = modifiedFoodValues.hunger; - float foodSaturationIncrement = modifiedFoodValues.getSaturationIncrement(); - - // restored hunger/saturation overlay while holding food - drawHungerOverlay(renderRenderEvent, mc, foodHunger, flashAlpha, FoodHelper.isRotten(heldItem, player)); - - // The render saturation overlay event maybe cancelled by other mods - assert saturationRenderEvent != null; - if (!saturationRenderEvent.isCanceled()) - { - int newFoodValue = stats.getFoodLevel() + foodHunger; - float newSaturationValue = stats.getSaturationLevel() + foodSaturationIncrement; - float saturationGained = newSaturationValue > newFoodValue ? newFoodValue - stats.getSaturationLevel() : foodSaturationIncrement; - // Redraw saturation overlay for gained - drawSaturationOverlay(saturationRenderEvent, mc, saturationGained, flashAlpha); - } - } - } - - public static void drawSaturationOverlay(float saturationGained, float saturationLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) - { - if (saturationLevel + saturationGained < 0) - return; - - enableAlpha(alpha); - - float modifiedSaturation = Math.max(0, Math.min(saturationLevel + saturationGained, 20)); - - int startSaturationBar = 0; - int endSaturationBar = (int) Math.ceil(modifiedSaturation / 2.0F); - - // when require rendering the gained saturation, start should relocation to current saturation tail. - if (saturationGained != 0) - startSaturationBar = (int) Math.max(saturationLevel / 2.0F, 0); - - int iconSize = 9; - - for (int i = startSaturationBar; i < endSaturationBar; ++i) - { - // gets the offset that needs to be render of icon - IntPoint offset = foodBarOffsets.get(i); - if (offset == null) - continue; - - int x = right + offset.x; - int y = top + offset.y; - - int v = 0; - int u = 0; - - float effectiveSaturationOfBar = (modifiedSaturation / 2.0F) - i; - - if (effectiveSaturationOfBar >= 1) - u = 3 * iconSize; - else if (effectiveSaturationOfBar > .5) - u = 2 * iconSize; - else if (effectiveSaturationOfBar > .25) - u = 1 * iconSize; - - guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, v, iconSize, iconSize); - } - - // rebind default icons - RenderSystem.setShaderTexture(0, TextureHelper.MC_ICONS); - disableAlpha(alpha); - } - - public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha, boolean useRottenTextures) - { - if (hungerRestored <= 0) - return; - - enableAlpha(alpha); - - int modifiedFood = Math.max(0, Math.min(20, foodLevel + hungerRestored)); - - int startFoodBars = Math.max(0, foodLevel / 2); - int endFoodBars = (int) Math.ceil(modifiedFood / 2.0F); - - int iconStartOffset = 16; - int iconSize = 9; - - for (int i = startFoodBars; i < endFoodBars; ++i) - { - // gets the offset that needs to be render of icon - IntPoint offset = foodBarOffsets.get(i); - if (offset == null) - continue; - - int x = right + offset.x; - int y = top + offset.y; - - // location to normal food by default - int v = 3 * iconSize; - int u = iconStartOffset + 4 * iconSize; - int ub = iconStartOffset + 1 * iconSize; - - // relocation to rotten food - if (useRottenTextures) - { - u += 4 * iconSize; - ub += 12 * iconSize; - } - - // relocation to half food - if (i * 2 + 1 == modifiedFood) - u += 1 * iconSize; - - // very faint background - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha * 0.25F); - guiGraphics.blit(TextureHelper.MC_ICONS, x, y, ub, v, iconSize, iconSize); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); - - guiGraphics.blit(TextureHelper.MC_ICONS, x, y, u, v, iconSize, iconSize); - } - - disableAlpha(alpha); - } - - public static void drawHealthOverlay(float health, float modifiedHealth, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) - { - if (modifiedHealth <= health) - return; - - enableAlpha(alpha); - - int fixedModifiedHealth = (int) Math.ceil(modifiedHealth); - boolean isHardcore = mc.player.level() != null && mc.player.level().getLevelData().isHardcore(); - - int startHealthBars = (int) Math.max(0, (Math.ceil(health) / 2.0F)); - int endHealthBars = (int) Math.max(0, Math.ceil(modifiedHealth / 2.0F)); - - int iconStartOffset = 16; - int iconSize = 9; - - for (int i = startHealthBars; i < endHealthBars; ++i) - { - // gets the offset that needs to be render of icon - IntPoint offset = healthBarOffsets.get(i); - if (offset == null) - continue; - - int x = right + offset.x; - int y = top + offset.y; - - // location to full heart icon by default - int v = 0 * iconSize; - int u = iconStartOffset + 4 * iconSize; - int ub = iconStartOffset + 1 * iconSize; - - // relocation to half heart - if (i * 2 + 1 == fixedModifiedHealth) - u += 1 * iconSize; - - // relocation to special heart of hardcore - if (isHardcore) - v = 5 * iconSize; - - //// apply the status effects of the player - //if (player.hasStatusEffect(StatusEffects.POISON)) { - // u += 4 * iconSize; - //} else if (player.hasStatusEffect(StatusEffects.WITHER)) { - // u += 8 * iconSize; - //} - - // very faint background - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha * 0.25F); - guiGraphics.blit(TextureHelper.MC_ICONS, x, y, ub, v, iconSize, iconSize); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); - - guiGraphics.blit(TextureHelper.MC_ICONS, x, y, u, v, iconSize, iconSize); - } - - disableAlpha(alpha); - } - - public static void drawExhaustionOverlay(float exhaustion, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) - { - float maxExhaustion = HungerHelper.getMaxExhaustion(mc.player); - // clamp between 0 and 1 - float ratio = Math.min(1, Math.max(0, exhaustion / maxExhaustion)); - int width = (int) (ratio * 81); - int height = 9; - - enableAlpha(.75f); - guiGraphics.blit(TextureHelper.MOD_ICONS, right - width, top, 81 - width, 18, width, height); - disableAlpha(.75f); - - // rebind default icons - RenderSystem.setShaderTexture(0, TextureHelper.MC_ICONS); - } - - - public static void enableAlpha(float alpha) - { - RenderSystem.enableBlend(); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); - RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - } - - public static void disableAlpha(float alpha) - { - RenderSystem.disableBlend(); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - } - - @SubscribeEvent - public void onClientTick(TickEvent.ClientTickEvent event) - { - if (event.phase != TickEvent.Phase.END) - return; - - unclampedFlashAlpha += alphaDir * 0.125f; - if (unclampedFlashAlpha >= 1.5f) - { - alphaDir = -1; - } - else if (unclampedFlashAlpha <= -0.5f) - { - alphaDir = 1; - } - flashAlpha = Math.max(0F, Math.min(1F, unclampedFlashAlpha)) * Math.max(0F, Math.min(1F, ModConfig.MAX_HUD_OVERLAY_FLASH_ALPHA.get().floatValue())); - } - - public static void resetFlash() - { - unclampedFlashAlpha = flashAlpha = 0f; - alphaDir = 1; - } - - private static void drawSaturationOverlay(HUDOverlayEvent.Saturation event, Minecraft mc, float saturationGained, float alpha) - { - drawSaturationOverlay(saturationGained, event.saturationLevel, mc, event.guiGraphics, event.x, event.y, alpha); - } - - private static void drawHungerOverlay(HUDOverlayEvent.HungerRestored event, Minecraft mc, int hunger, float alpha, boolean useRottenTextures) - { - drawHungerOverlay(hunger, event.currentFoodLevel, mc, event.guiGraphics, event.x, event.y, alpha, useRottenTextures); - } - - private static void drawHealthOverlay(HUDOverlayEvent.HealthRestored event, Minecraft mc, float alpha) - { - drawHealthOverlay(mc.player.getHealth(), event.modifiedHealth, mc, event.guiGraphics, event.x, event.y, alpha); - } - - private static void drawExhaustionOverlay(HUDOverlayEvent.Exhaustion event, Minecraft mc, float alpha) - { - drawExhaustionOverlay(event.exhaustion, mc, event.guiGraphics, event.x, event.y, alpha); - } - - private static boolean shouldRenderAnyOverlays() - { - return ModConfig.SHOW_FOOD_VALUES_OVERLAY.get() || ModConfig.SHOW_SATURATION_OVERLAY.get() || ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get(); - } - - private static boolean shouldShowEstimatedHealth(ItemStack hoveredStack, FoodValues modifiedFoodValues) - { - // then configuration cancel the render event - if (!ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get()) - return false; - - Minecraft mc = Minecraft.getInstance(); - Player player = mc.player; - FoodData stats = player.getFoodData(); - - // in the `PEACEFUL` mode, health will restore faster - if (player.level().getDifficulty() == Difficulty.PEACEFUL) - return false; - - // when player has any changes health amount by any case can't show estimated health - // because player will confused how much of restored/damaged healths - if (stats.getFoodLevel() >= 18) - return false; - - if (player.hasEffect(MobEffects.POISON)) - return false; - - if (player.hasEffect(MobEffects.WITHER)) - return false; - - if (player.hasEffect(MobEffects.REGENERATION)) - return false; - - return true; - } - - private static void generateHealthBarOffsets(int top, int left, int right, int ticks, Player player) - { - // hard code in `InGameHUD` - random.setSeed((long) (ticks * 312871L)); - - final int preferHealthBars = 10; - final float maxHealth = player.getMaxHealth(); - final float absorptionHealth = (float) Math.ceil(player.getAbsorptionAmount()); - - int healthBars = (int) Math.ceil((maxHealth + absorptionHealth) / 2.0F); - // When maxHealth + absorptionHealth is greater than Integer.INT_MAX, - // Minecraft will disable heart rendering due to a quirk of MathHelper.ceil. - // We have a much lower threshold since there's no reason to get the offsets - // for thousands of hearts. - // Note: Infinite and > INT_MAX absorption has been seen in the wild. - // This will effectively disable rendering whenever health is unexpectedly large. - if (healthBars < 0 || healthBars > 1000) { - healthBarOffsets.setSize(0); - return; - } - - int healthRows = (int) Math.ceil((float) healthBars / 10.0F); - - int healthRowHeight = Math.max(10 - (healthRows - 2), 3); - - boolean shouldAnimatedHealth = false; - - // when some mods using custom render, we need to least provide an option to cancel animation - if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) - { - // in vanilla health is too low (below 5) will show heartbeat animation - // when regeneration will also show heartbeat animation, but we don't need now - shouldAnimatedHealth = Math.ceil(player.getHealth()) <= 4; - } - - // adjust the size - if (healthBarOffsets.size() != healthBars) - healthBarOffsets.setSize(healthBars); - - // left alignment, multiple rows, reverse - for (int i = healthBars - 1; i >= 0; --i) - { - int row = (int) Math.ceil((float) (i + 1) / (float) preferHealthBars) - 1; - int x = left + i % preferHealthBars * 8; - int y = top - row * healthRowHeight; - // apply the animated offset - if (shouldAnimatedHealth) - y += random.nextInt(2); - - // reuse the point object to reduce memory usage - IntPoint point = healthBarOffsets.get(i); - if (point == null) - { - point = new IntPoint(); - healthBarOffsets.set(i, point); - } - - point.x = x - left; - point.y = y - top; - } - } - - private static void generateHungerBarOffsets(int top, int left, int right, int ticks, Player player) - { - final int preferFoodBars = 10; - - boolean shouldAnimatedFood = false; - - // when some mods using custom render, we need to least provide an option to cancel animation - if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) - { - FoodData stats = player.getFoodData(); - - // in vanilla saturation level is zero will show hunger animation - float saturationLevel = stats.getSaturationLevel(); - int foodLevel = stats.getFoodLevel(); - shouldAnimatedFood = saturationLevel <= 0.0F && ticks % (foodLevel * 3 + 1) == 0; - } - - if (foodBarOffsets.size() != preferFoodBars) - foodBarOffsets.setSize(preferFoodBars); - - // right alignment, single row - for (int i = 0; i < preferFoodBars; ++i) - { - int x = right - i * 8 - 9; - int y = top; - - // apply the animated offset - if (shouldAnimatedFood) - y += random.nextInt(3) - 1; - - // reuse the point object to reduce memory usage - IntPoint point = foodBarOffsets.get(i); - if (point == null) - { - point = new IntPoint(); - foodBarOffsets.set(i, point); - } - - point.x = x - right; - point.y = y - top; - } - } + private static float unclampedFlashAlpha = 0f; + private static float flashAlpha = 0f; + private static byte alphaDir = 1; + protected static int foodIconsOffset; + + public static final Vector healthBarOffsets = new Vector<>(); + public static final Vector foodBarOffsets = new Vector<>(); + + private static final Random random = new Random(); + + public static void init() + { + MinecraftForge.EVENT_BUS.register(new HUDOverlayHandler()); + } + + static ResourceLocation FOOD_LEVEL_ELEMENT = new ResourceLocation("minecraft", "food_level"); + static ResourceLocation PLAYER_HEALTH_ELEMENT = new ResourceLocation("minecraft", "player_health"); + + @SubscribeEvent + public void onRenderGuiOverlayPre(RenderGuiOverlayEvent.Pre event) + { + if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) + { + Minecraft mc = Minecraft.getInstance(); + ForgeGui gui = (ForgeGui) mc.gui; + boolean isMounted = mc.player.getVehicle() instanceof LivingEntity; + if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) + { + renderExhaustion(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight()); + } + } + } + + @SubscribeEvent + public void onRenderGuiOverlayPost(RenderGuiOverlayEvent.Post event) + { + if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) + { + Minecraft mc = Minecraft.getInstance(); + ForgeGui gui = (ForgeGui) mc.gui; + boolean isMounted = mc.player.getVehicle() instanceof LivingEntity; + if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) + { + renderFoodOrHealthOverlay(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight(), RenderOverlayType.FOOD); + } + } + else if (event.getOverlay() == GuiOverlayManager.findOverlay(PLAYER_HEALTH_ELEMENT)) + { + Minecraft mc = Minecraft.getInstance(); + ForgeGui gui = (ForgeGui) mc.gui; + if (!mc.options.hideGui && gui.shouldDrawSurvivalElements()) + { + renderFoodOrHealthOverlay(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight(), RenderOverlayType.HEALTH); + } + } + } + + public static void renderExhaustion(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight) + { + foodIconsOffset = gui.rightHeight; + + if (!ModConfig.SHOW_FOOD_EXHAUSTION_UNDERLAY.get()) + return; + + Minecraft mc = Minecraft.getInstance(); + Player player = mc.player; + assert player != null; + + int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; + int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; + float exhaustion = player.getFoodData().getExhaustionLevel(); + + // Notify everyone that we should render exhaustion hud overlay + HUDOverlayEvent.Exhaustion renderEvent = new HUDOverlayEvent.Exhaustion(exhaustion, right, top, guiGraphics); + MinecraftForge.EVENT_BUS.post(renderEvent); + if (!renderEvent.isCanceled()) + drawExhaustionOverlay(renderEvent, mc, 1f); + } + + enum RenderOverlayType + { + HEALTH, + FOOD, + } + + public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight, RenderOverlayType type) + { + if (!shouldRenderAnyOverlays()) + return; + + Minecraft mc = Minecraft.getInstance(); + Player player = mc.player; + assert player != null; + FoodData stats = player.getFoodData(); + + int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; + int left = mc.getWindow().getGuiScaledWidth() / 2 - 91; // left of health bar + int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; // right of food bar + + if (type == RenderOverlayType.HEALTH) + generateHealthBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); + if (type == RenderOverlayType.FOOD) + generateHungerBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); + + HUDOverlayEvent.Saturation saturationRenderEvent = null; + if (type == RenderOverlayType.FOOD) + { + saturationRenderEvent = new HUDOverlayEvent.Saturation(stats.getSaturationLevel(), right, top, guiGraphics); + + // cancel render overlay event when configuration disabled. + if (!ModConfig.SHOW_SATURATION_OVERLAY.get()) + saturationRenderEvent.setCanceled(true); + + // notify everyone that we should render saturation hud overlay + if (!saturationRenderEvent.isCanceled()) + MinecraftForge.EVENT_BUS.post(saturationRenderEvent); + + // the render saturation event maybe cancelled by other mods + if (!saturationRenderEvent.isCanceled()) + drawSaturationOverlay(saturationRenderEvent, mc, 0, 1f); + } + + // try to get the item stack in the player hand + ItemStack heldItem = player.getMainHandItem(); + if (ModConfig.SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND.get() && !FoodHelper.canConsume(heldItem, player)) + heldItem = player.getOffhandItem(); + + boolean shouldRenderHeldItemValues = !heldItem.isEmpty() && FoodHelper.canConsume(heldItem, player); + if (!shouldRenderHeldItemValues) + { + resetFlash(); + return; + } + + FoodValues modifiedFoodValues = FoodHelper.getModifiedFoodValues(heldItem, player); + FoodValuesEvent foodValuesEvent = new FoodValuesEvent(player, heldItem, FoodHelper.getDefaultFoodValues(heldItem, player), modifiedFoodValues); + MinecraftForge.EVENT_BUS.post(foodValuesEvent); + modifiedFoodValues = foodValuesEvent.modifiedFoodValues; + + if (type == RenderOverlayType.HEALTH) + { + // Offsets size is set to zero intentionally to disable rendering when health is infinite. + if (healthBarOffsets.size() == 0) + return; + + if (!shouldShowEstimatedHealth(heldItem, modifiedFoodValues)) + return; + + float foodHealthIncrement = FoodHelper.getEstimatedHealthIncrement(heldItem, modifiedFoodValues, player); + float currentHealth = player.getHealth(); + float modifiedHealth = Math.min(currentHealth + foodHealthIncrement, player.getMaxHealth()); + + // only create object when the estimated health is successfully + HUDOverlayEvent.HealthRestored healthRenderEvent = null; + if (currentHealth < modifiedHealth) + healthRenderEvent = new HUDOverlayEvent.HealthRestored(modifiedHealth, heldItem, modifiedFoodValues, left, top, guiGraphics); + + // notify everyone that we should render estimated health hud + if (healthRenderEvent != null) + MinecraftForge.EVENT_BUS.post(healthRenderEvent); + + if (healthRenderEvent != null && !healthRenderEvent.isCanceled()) + drawHealthOverlay(healthRenderEvent, mc, flashAlpha); + } + else if (type == RenderOverlayType.FOOD) + { + if (!ModConfig.SHOW_FOOD_VALUES_OVERLAY.get()) + return; + + // notify everyone that we should render hunger hud overlay + HUDOverlayEvent.HungerRestored renderRenderEvent = new HUDOverlayEvent.HungerRestored(stats.getFoodLevel(), heldItem, modifiedFoodValues, right, top, guiGraphics); + MinecraftForge.EVENT_BUS.post(renderRenderEvent); + if (renderRenderEvent.isCanceled()) + return; + + // calculate the final hunger and saturation + int foodHunger = modifiedFoodValues.hunger; + float foodSaturationIncrement = modifiedFoodValues.getSaturationIncrement(); + + // restored hunger/saturation overlay while holding food + drawHungerOverlay(renderRenderEvent, mc, foodHunger, flashAlpha, FoodHelper.isRotten(heldItem, player)); + + // The render saturation overlay event maybe cancelled by other mods + assert saturationRenderEvent != null; + if (!saturationRenderEvent.isCanceled()) + { + int newFoodValue = stats.getFoodLevel() + foodHunger; + float newSaturationValue = stats.getSaturationLevel() + foodSaturationIncrement; + float saturationGained = newSaturationValue > newFoodValue ? newFoodValue - stats.getSaturationLevel() : foodSaturationIncrement; + // Redraw saturation overlay for gained + drawSaturationOverlay(saturationRenderEvent, mc, saturationGained, flashAlpha); + } + } + } + + public static void drawSaturationOverlay(float saturationGained, float saturationLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) + { + if (saturationLevel + saturationGained < 0) + return; + + enableAlpha(alpha); + + float modifiedSaturation = Math.max(0, Math.min(saturationLevel + saturationGained, 20)); + + int startSaturationBar = 0; + int endSaturationBar = (int) Math.ceil(modifiedSaturation / 2.0F); + + // when require rendering the gained saturation, start should relocation to current saturation tail. + if (saturationGained != 0) + startSaturationBar = (int) Math.max(saturationLevel / 2.0F, 0); + + int iconSize = 9; + + for (int i = startSaturationBar; i < endSaturationBar; ++i) + { + // gets the offset that needs to be render of icon + IntPoint offset = foodBarOffsets.get(i); + if (offset == null) + continue; + + int x = right + offset.x; + int y = top + offset.y; + + int v = 0; + int u = 0; + + float effectiveSaturationOfBar = (modifiedSaturation / 2.0F) - i; + + if (effectiveSaturationOfBar >= 1) + u = 3 * iconSize; + else if (effectiveSaturationOfBar > .5) + u = 2 * iconSize; + else if (effectiveSaturationOfBar > .25) + u = 1 * iconSize; + + guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, v, iconSize, iconSize); + } + + // rebind default icons + RenderSystem.setShaderTexture(0, TextureHelper.MC_ICONS); + disableAlpha(alpha); + } + + public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha, boolean useRottenTextures) + { + if (hungerRestored <= 0) + return; + + enableAlpha(alpha); + + int modifiedFood = Math.max(0, Math.min(20, foodLevel + hungerRestored)); + + int startFoodBars = Math.max(0, foodLevel / 2); + int endFoodBars = (int) Math.ceil(modifiedFood / 2.0F); + + int iconStartOffset = 16; + int iconSize = 9; + + for (int i = startFoodBars; i < endFoodBars; ++i) + { + // gets the offset that needs to be render of icon + IntPoint offset = foodBarOffsets.get(i); + if (offset == null) + continue; + + int x = right + offset.x; + int y = top + offset.y; + + // location to normal food by default + int v = 3 * iconSize; + int u = iconStartOffset + 4 * iconSize; + int ub = iconStartOffset + 1 * iconSize; + + // relocation to rotten food + if (useRottenTextures) + { + u += 4 * iconSize; + ub += 12 * iconSize; + } + + // relocation to half food + if (i * 2 + 1 == modifiedFood) + u += 1 * iconSize; + + // very faint background + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha * 0.25F); + guiGraphics.blit(TextureHelper.MC_ICONS, x, y, ub, v, iconSize, iconSize); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); + + guiGraphics.blit(TextureHelper.MC_ICONS, x, y, u, v, iconSize, iconSize); + } + + disableAlpha(alpha); + } + + public static void drawHealthOverlay(float health, float modifiedHealth, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) + { + if (modifiedHealth <= health) + return; + + enableAlpha(alpha); + + int fixedModifiedHealth = (int) Math.ceil(modifiedHealth); + boolean isHardcore = mc.player.level() != null && mc.player.level().getLevelData().isHardcore(); + + int startHealthBars = (int) Math.max(0, (Math.ceil(health) / 2.0F)); + int endHealthBars = (int) Math.max(0, Math.ceil(modifiedHealth / 2.0F)); + + int iconStartOffset = 16; + int iconSize = 9; + + for (int i = startHealthBars; i < endHealthBars; ++i) + { + // gets the offset that needs to be render of icon + IntPoint offset = healthBarOffsets.get(i); + if (offset == null) + continue; + + int x = right + offset.x; + int y = top + offset.y; + + // location to full heart icon by default + int v = 0 * iconSize; + int u = iconStartOffset + 4 * iconSize; + int ub = iconStartOffset + 1 * iconSize; + + // relocation to half heart + if (i * 2 + 1 == fixedModifiedHealth) + u += 1 * iconSize; + + // relocation to special heart of hardcore + if (isHardcore) + v = 5 * iconSize; + + //// apply the status effects of the player + //if (player.hasStatusEffect(StatusEffects.POISON)) { + // u += 4 * iconSize; + //} else if (player.hasStatusEffect(StatusEffects.WITHER)) { + // u += 8 * iconSize; + //} + + // very faint background + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha * 0.25F); + guiGraphics.blit(TextureHelper.MC_ICONS, x, y, ub, v, iconSize, iconSize); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); + + guiGraphics.blit(TextureHelper.MC_ICONS, x, y, u, v, iconSize, iconSize); + } + + disableAlpha(alpha); + } + + public static void drawExhaustionOverlay(float exhaustion, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) + { + float maxExhaustion = HungerHelper.getMaxExhaustion(mc.player); + // clamp between 0 and 1 + float ratio = Math.min(1, Math.max(0, exhaustion / maxExhaustion)); + int width = (int) (ratio * 81); + int height = 9; + + enableAlpha(.75f); + guiGraphics.blit(TextureHelper.MOD_ICONS, right - width, top, 81 - width, 18, width, height); + disableAlpha(.75f); + + // rebind default icons + RenderSystem.setShaderTexture(0, TextureHelper.MC_ICONS); + } + + + public static void enableAlpha(float alpha) + { + RenderSystem.enableBlend(); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); + RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + } + + public static void disableAlpha(float alpha) + { + RenderSystem.disableBlend(); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + } + + @SubscribeEvent + public void onClientTick(TickEvent.ClientTickEvent event) + { + if (event.phase != TickEvent.Phase.END) + return; + + unclampedFlashAlpha += alphaDir * 0.125f; + if (unclampedFlashAlpha >= 1.5f) + { + alphaDir = -1; + } + else if (unclampedFlashAlpha <= -0.5f) + { + alphaDir = 1; + } + flashAlpha = Math.max(0F, Math.min(1F, unclampedFlashAlpha)) * Math.max(0F, Math.min(1F, ModConfig.MAX_HUD_OVERLAY_FLASH_ALPHA.get().floatValue())); + } + + public static void resetFlash() + { + unclampedFlashAlpha = flashAlpha = 0f; + alphaDir = 1; + } + + private static void drawSaturationOverlay(HUDOverlayEvent.Saturation event, Minecraft mc, float saturationGained, float alpha) + { + drawSaturationOverlay(saturationGained, event.saturationLevel, mc, event.guiGraphics, event.x, event.y, alpha); + } + + private static void drawHungerOverlay(HUDOverlayEvent.HungerRestored event, Minecraft mc, int hunger, float alpha, boolean useRottenTextures) + { + drawHungerOverlay(hunger, event.currentFoodLevel, mc, event.guiGraphics, event.x, event.y, alpha, useRottenTextures); + } + + private static void drawHealthOverlay(HUDOverlayEvent.HealthRestored event, Minecraft mc, float alpha) + { + drawHealthOverlay(mc.player.getHealth(), event.modifiedHealth, mc, event.guiGraphics, event.x, event.y, alpha); + } + + private static void drawExhaustionOverlay(HUDOverlayEvent.Exhaustion event, Minecraft mc, float alpha) + { + drawExhaustionOverlay(event.exhaustion, mc, event.guiGraphics, event.x, event.y, alpha); + } + + private static boolean shouldRenderAnyOverlays() + { + return ModConfig.SHOW_FOOD_VALUES_OVERLAY.get() || ModConfig.SHOW_SATURATION_OVERLAY.get() || ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get(); + } + + private static boolean shouldShowEstimatedHealth(ItemStack hoveredStack, FoodValues modifiedFoodValues) + { + // then configuration cancel the render event + if (!ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get()) + return false; + + Minecraft mc = Minecraft.getInstance(); + Player player = mc.player; + FoodData stats = player.getFoodData(); + + // in the `PEACEFUL` mode, health will restore faster + if (player.level().getDifficulty() == Difficulty.PEACEFUL) + return false; + + // when player has any changes health amount by any case can't show estimated health + // because player will confused how much of restored/damaged healths + if (stats.getFoodLevel() >= 18) + return false; + + if (player.hasEffect(MobEffects.POISON)) + return false; + + if (player.hasEffect(MobEffects.WITHER)) + return false; + + if (player.hasEffect(MobEffects.REGENERATION)) + return false; + + return true; + } + + private static void generateHealthBarOffsets(int top, int left, int right, int ticks, Player player) + { + // hard code in `InGameHUD` + random.setSeed((long) (ticks * 312871L)); + + final int preferHealthBars = 10; + final float maxHealth = player.getMaxHealth(); + final float absorptionHealth = (float) Math.ceil(player.getAbsorptionAmount()); + + int healthBars = (int) Math.ceil((maxHealth + absorptionHealth) / 2.0F); + // When maxHealth + absorptionHealth is greater than Integer.INT_MAX, + // Minecraft will disable heart rendering due to a quirk of MathHelper.ceil. + // We have a much lower threshold since there's no reason to get the offsets + // for thousands of hearts. + // Note: Infinite and > INT_MAX absorption has been seen in the wild. + // This will effectively disable rendering whenever health is unexpectedly large. + if (healthBars < 0 || healthBars > 1000) { + healthBarOffsets.setSize(0); + return; + } + + int healthRows = (int) Math.ceil((float) healthBars / 10.0F); + + int healthRowHeight = Math.max(10 - (healthRows - 2), 3); + + boolean shouldAnimatedHealth = false; + + // when some mods using custom render, we need to least provide an option to cancel animation + if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) + { + // in vanilla health is too low (below 5) will show heartbeat animation + // when regeneration will also show heartbeat animation, but we don't need now + shouldAnimatedHealth = Math.ceil(player.getHealth()) <= 4; + } + + // adjust the size + if (healthBarOffsets.size() != healthBars) + healthBarOffsets.setSize(healthBars); + + // left alignment, multiple rows, reverse + for (int i = healthBars - 1; i >= 0; --i) + { + int row = (int) Math.ceil((float) (i + 1) / (float) preferHealthBars) - 1; + int x = left + i % preferHealthBars * 8; + int y = top - row * healthRowHeight; + // apply the animated offset + if (shouldAnimatedHealth) + y += random.nextInt(2); + + // reuse the point object to reduce memory usage + IntPoint point = healthBarOffsets.get(i); + if (point == null) + { + point = new IntPoint(); + healthBarOffsets.set(i, point); + } + + point.x = x - left; + point.y = y - top; + } + } + + private static void generateHungerBarOffsets(int top, int left, int right, int ticks, Player player) + { + final int preferFoodBars = 10; + + boolean shouldAnimatedFood = false; + + // when some mods using custom render, we need to least provide an option to cancel animation + if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) + { + FoodData stats = player.getFoodData(); + + // in vanilla saturation level is zero will show hunger animation + float saturationLevel = stats.getSaturationLevel(); + int foodLevel = stats.getFoodLevel(); + shouldAnimatedFood = saturationLevel <= 0.0F && ticks % (foodLevel * 3 + 1) == 0; + } + + if (foodBarOffsets.size() != preferFoodBars) + foodBarOffsets.setSize(preferFoodBars); + + // right alignment, single row + for (int i = 0; i < preferFoodBars; ++i) + { + int x = right - i * 8 - 9; + int y = top; + + // apply the animated offset + if (shouldAnimatedFood) + y += random.nextInt(3) - 1; + + // reuse the point object to reduce memory usage + IntPoint point = foodBarOffsets.get(i); + if (point == null) + { + point = new IntPoint(); + foodBarOffsets.set(i, point); + } + + point.x = x - right; + point.y = y - top; + } + } } From 79630db83d3c7200b6fcfccb6d345e004fe67a8f Mon Sep 17 00:00:00 2001 From: Waterpicker Date: Fri, 30 Jan 2026 03:51:26 -0600 Subject: [PATCH 02/11] Colored saturation levels up to 100 --- .../appleskin/client/HUDOverlayHandler.java | 73 +++++++++++++++---- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/java/squeek/appleskin/client/HUDOverlayHandler.java b/java/squeek/appleskin/client/HUDOverlayHandler.java index 681189b..9175a03 100644 --- a/java/squeek/appleskin/client/HUDOverlayHandler.java +++ b/java/squeek/appleskin/client/HUDOverlayHandler.java @@ -236,20 +236,28 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio enableAlpha(alpha); - float modifiedSaturation = Math.max(0, Math.min(saturationLevel + saturationGained, 20)); + float modifiedSaturation = saturationLevel + saturationGained; + int iconSize = 9; + + int currentLayer = (int) (modifiedSaturation / 20); + + float layerSaturation = modifiedSaturation % 20; + if (layerSaturation == 0 && modifiedSaturation > 0) + layerSaturation = 20; int startSaturationBar = 0; - int endSaturationBar = (int) Math.ceil(modifiedSaturation / 2.0F); + int endSaturationBar = 10; - // when require rendering the gained saturation, start should relocation to current saturation tail. if (saturationGained != 0) - startSaturationBar = (int) Math.max(saturationLevel / 2.0F, 0); - - int iconSize = 9; + { + float currentSaturationInLayer = saturationLevel % 20; + if (currentSaturationInLayer == 0 && saturationLevel > 0) + currentSaturationInLayer = 20; + startSaturationBar = (int) (currentSaturationInLayer / 2.0F); + } for (int i = startSaturationBar; i < endSaturationBar; ++i) { - // gets the offset that needs to be render of icon IntPoint offset = foodBarOffsets.get(i); if (offset == null) continue; @@ -257,26 +265,61 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio int x = right + offset.x; int y = top + offset.y; - int v = 0; - int u = 0; - - float effectiveSaturationOfBar = (modifiedSaturation / 2.0F) - i; + float effectiveSaturationOfBar = (layerSaturation / 2.0F) - i; + int u = 0; if (effectiveSaturationOfBar >= 1) u = 3 * iconSize; - else if (effectiveSaturationOfBar > .5) + else if (effectiveSaturationOfBar >= 0.5) u = 2 * iconSize; - else if (effectiveSaturationOfBar > .25) + else if (effectiveSaturationOfBar > 0.25) u = 1 * iconSize; - guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, v, iconSize, iconSize); + int currentColor = getLayerColor(currentLayer, alpha); + + if (currentLayer > 0) + { + int previousColor = getLayerColor(currentLayer - 1, alpha); + + RenderSystem.setShaderColor( + ((previousColor >> 16) & 0xFF) / 255f, + ((previousColor >> 8) & 0xFF) / 255f, + (previousColor & 0xFF) / 255f, + ((previousColor >> 24) & 0xFF) / 255f + ); + + guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, 3 * iconSize, 0, iconSize, iconSize); + } + + RenderSystem.setShaderColor( + ((currentColor >> 16) & 0xFF) / 255f, + ((currentColor >> 8) & 0xFF) / 255f, + (currentColor & 0xFF) / 255f, + ((currentColor >> 24) & 0xFF) / 255f + ); + + guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, 0, iconSize, iconSize); } - // rebind default icons + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); RenderSystem.setShaderTexture(0, TextureHelper.MC_ICONS); disableAlpha(alpha); } + private static int getLayerColor(int layer, float alpha) + { + int alphaValue = (int) (alpha * 255) << 24; + + return switch (layer) { + case 0 -> alphaValue | 0xFFFF99; + case 1 -> alphaValue | 0xFFCC00; + case 2 -> alphaValue | 0xFF9900; + case 3 -> alphaValue | 0xFF6600; + case 4 -> alphaValue | 0xFF3300; + default -> alphaValue | 0xFF0000; + }; + } + public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha, boolean useRottenTextures) { if (hungerRestored <= 0) From 8dbb32081048b7234e0f0487355a82bad2de3129 Mon Sep 17 00:00:00 2001 From: Mysticpasta1 Date: Fri, 30 Jan 2026 14:16:32 -0600 Subject: [PATCH 03/11] support for saturation uncapped --- .../appleskin/client/HUDOverlayHandler.java | 502 ++++++------------ 1 file changed, 156 insertions(+), 346 deletions(-) diff --git a/java/squeek/appleskin/client/HUDOverlayHandler.java b/java/squeek/appleskin/client/HUDOverlayHandler.java index 9175a03..db29490 100644 --- a/java/squeek/appleskin/client/HUDOverlayHandler.java +++ b/java/squeek/appleskin/client/HUDOverlayHandler.java @@ -38,10 +38,8 @@ public class HUDOverlayHandler private static float flashAlpha = 0f; private static byte alphaDir = 1; protected static int foodIconsOffset; - public static final Vector healthBarOffsets = new Vector<>(); public static final Vector foodBarOffsets = new Vector<>(); - private static final Random random = new Random(); public static void init() @@ -94,19 +92,14 @@ else if (event.getOverlay() == GuiOverlayManager.findOverlay(PLAYER_HEALTH_ELEME public static void renderExhaustion(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight) { foodIconsOffset = gui.rightHeight; - if (!ModConfig.SHOW_FOOD_EXHAUSTION_UNDERLAY.get()) return; - Minecraft mc = Minecraft.getInstance(); Player player = mc.player; assert player != null; - int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; float exhaustion = player.getFoodData().getExhaustionLevel(); - - // Notify everyone that we should render exhaustion hud overlay HUDOverlayEvent.Exhaustion renderEvent = new HUDOverlayEvent.Exhaustion(exhaustion, right, top, guiGraphics); MinecraftForge.EVENT_BUS.post(renderEvent); if (!renderEvent.isCanceled()) @@ -123,78 +116,55 @@ public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphi { if (!shouldRenderAnyOverlays()) return; - Minecraft mc = Minecraft.getInstance(); Player player = mc.player; assert player != null; FoodData stats = player.getFoodData(); - int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; - int left = mc.getWindow().getGuiScaledWidth() / 2 - 91; // left of health bar - int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; // right of food bar - + int left = mc.getWindow().getGuiScaledWidth() / 2 - 91; + int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; if (type == RenderOverlayType.HEALTH) generateHealthBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); if (type == RenderOverlayType.FOOD) generateHungerBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); - HUDOverlayEvent.Saturation saturationRenderEvent = null; if (type == RenderOverlayType.FOOD) { saturationRenderEvent = new HUDOverlayEvent.Saturation(stats.getSaturationLevel(), right, top, guiGraphics); - - // cancel render overlay event when configuration disabled. if (!ModConfig.SHOW_SATURATION_OVERLAY.get()) saturationRenderEvent.setCanceled(true); - - // notify everyone that we should render saturation hud overlay if (!saturationRenderEvent.isCanceled()) MinecraftForge.EVENT_BUS.post(saturationRenderEvent); - - // the render saturation event maybe cancelled by other mods if (!saturationRenderEvent.isCanceled()) drawSaturationOverlay(saturationRenderEvent, mc, 0, 1f); } - - // try to get the item stack in the player hand ItemStack heldItem = player.getMainHandItem(); if (ModConfig.SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND.get() && !FoodHelper.canConsume(heldItem, player)) heldItem = player.getOffhandItem(); - boolean shouldRenderHeldItemValues = !heldItem.isEmpty() && FoodHelper.canConsume(heldItem, player); if (!shouldRenderHeldItemValues) { resetFlash(); return; } - FoodValues modifiedFoodValues = FoodHelper.getModifiedFoodValues(heldItem, player); FoodValuesEvent foodValuesEvent = new FoodValuesEvent(player, heldItem, FoodHelper.getDefaultFoodValues(heldItem, player), modifiedFoodValues); MinecraftForge.EVENT_BUS.post(foodValuesEvent); modifiedFoodValues = foodValuesEvent.modifiedFoodValues; - if (type == RenderOverlayType.HEALTH) { - // Offsets size is set to zero intentionally to disable rendering when health is infinite. if (healthBarOffsets.size() == 0) return; - if (!shouldShowEstimatedHealth(heldItem, modifiedFoodValues)) return; - float foodHealthIncrement = FoodHelper.getEstimatedHealthIncrement(heldItem, modifiedFoodValues, player); float currentHealth = player.getHealth(); float modifiedHealth = Math.min(currentHealth + foodHealthIncrement, player.getMaxHealth()); - - // only create object when the estimated health is successfully HUDOverlayEvent.HealthRestored healthRenderEvent = null; if (currentHealth < modifiedHealth) healthRenderEvent = new HUDOverlayEvent.HealthRestored(modifiedHealth, heldItem, modifiedFoodValues, left, top, guiGraphics); - - // notify everyone that we should render estimated health hud if (healthRenderEvent != null) MinecraftForge.EVENT_BUS.post(healthRenderEvent); - if (healthRenderEvent != null && !healthRenderEvent.isCanceled()) drawHealthOverlay(healthRenderEvent, mc, flashAlpha); } @@ -202,28 +172,19 @@ else if (type == RenderOverlayType.FOOD) { if (!ModConfig.SHOW_FOOD_VALUES_OVERLAY.get()) return; - - // notify everyone that we should render hunger hud overlay HUDOverlayEvent.HungerRestored renderRenderEvent = new HUDOverlayEvent.HungerRestored(stats.getFoodLevel(), heldItem, modifiedFoodValues, right, top, guiGraphics); MinecraftForge.EVENT_BUS.post(renderRenderEvent); if (renderRenderEvent.isCanceled()) return; - - // calculate the final hunger and saturation int foodHunger = modifiedFoodValues.hunger; float foodSaturationIncrement = modifiedFoodValues.getSaturationIncrement(); - - // restored hunger/saturation overlay while holding food drawHungerOverlay(renderRenderEvent, mc, foodHunger, flashAlpha, FoodHelper.isRotten(heldItem, player)); - - // The render saturation overlay event maybe cancelled by other mods assert saturationRenderEvent != null; if (!saturationRenderEvent.isCanceled()) { int newFoodValue = stats.getFoodLevel() + foodHunger; float newSaturationValue = stats.getSaturationLevel() + foodSaturationIncrement; float saturationGained = newSaturationValue > newFoodValue ? newFoodValue - stats.getSaturationLevel() : foodSaturationIncrement; - // Redraw saturation overlay for gained drawSaturationOverlay(saturationRenderEvent, mc, saturationGained, flashAlpha); } } @@ -231,276 +192,194 @@ else if (type == RenderOverlayType.FOOD) public static void drawSaturationOverlay(float saturationGained, float saturationLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) { - if (saturationLevel + saturationGained < 0) - return; - + if (saturationLevel + saturationGained < 0) return; enableAlpha(alpha); - float modifiedSaturation = saturationLevel + saturationGained; int iconSize = 9; - - int currentLayer = (int) (modifiedSaturation / 20); - + int currentLayer = (int)(modifiedSaturation / 20); float layerSaturation = modifiedSaturation % 20; - if (layerSaturation == 0 && modifiedSaturation > 0) - layerSaturation = 20; - + if (layerSaturation == 0 && modifiedSaturation > 0) layerSaturation = 20; int startSaturationBar = 0; int endSaturationBar = 10; - if (saturationGained != 0) { float currentSaturationInLayer = saturationLevel % 20; - if (currentSaturationInLayer == 0 && saturationLevel > 0) - currentSaturationInLayer = 20; - startSaturationBar = (int) (currentSaturationInLayer / 2.0F); + if (currentSaturationInLayer == 0 && saturationLevel > 0) currentSaturationInLayer = 20; + startSaturationBar = (int)(currentSaturationInLayer / 2.0F); } - for (int i = startSaturationBar; i < endSaturationBar; ++i) { IntPoint offset = foodBarOffsets.get(i); - if (offset == null) - continue; - + if (offset == null) continue; int x = right + offset.x; int y = top + offset.y; - float effectiveSaturationOfBar = (layerSaturation / 2.0F) - i; - int u = 0; - if (effectiveSaturationOfBar >= 1) - u = 3 * iconSize; - else if (effectiveSaturationOfBar >= 0.5) - u = 2 * iconSize; - else if (effectiveSaturationOfBar > 0.25) - u = 1 * iconSize; - + if (effectiveSaturationOfBar >= 1) u = 3 * iconSize; + else if (effectiveSaturationOfBar >= 0.5) u = 2 * iconSize; + else if (effectiveSaturationOfBar > 0.25) u = 1 * iconSize; int currentColor = getLayerColor(currentLayer, alpha); - if (currentLayer > 0) { int previousColor = getLayerColor(currentLayer - 1, alpha); - - RenderSystem.setShaderColor( - ((previousColor >> 16) & 0xFF) / 255f, - ((previousColor >> 8) & 0xFF) / 255f, - (previousColor & 0xFF) / 255f, - ((previousColor >> 24) & 0xFF) / 255f - ); - + RenderSystem.setShaderColor(((previousColor >> 16) & 0xFF) / 255f, ((previousColor >> 8) & 0xFF) / 255f, (previousColor & 0xFF) / 255f, ((previousColor >> 24) & 0xFF) / 255f); guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, 3 * iconSize, 0, iconSize, iconSize); } - - RenderSystem.setShaderColor( - ((currentColor >> 16) & 0xFF) / 255f, - ((currentColor >> 8) & 0xFF) / 255f, - (currentColor & 0xFF) / 255f, - ((currentColor >> 24) & 0xFF) / 255f - ); - + RenderSystem.setShaderColor(((currentColor >> 16) & 0xFF) / 255f, ((currentColor >> 8) & 0xFF) / 255f, (currentColor & 0xFF) / 255f, ((currentColor >> 24) & 0xFF) / 255f); guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, 0, iconSize, iconSize); } - - RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + RenderSystem.setShaderColor(1f,1f,1f,1f); RenderSystem.setShaderTexture(0, TextureHelper.MC_ICONS); disableAlpha(alpha); } - private static int getLayerColor(int layer, float alpha) + private static int hsvToRgb(float h, float s, float v) { - int alphaValue = (int) (alpha * 255) << 24; + int r,g,b; + int i = (int)(h*6); + float f = h*6-i; + float p = v*(1-s); + float q = v*(1-f*s); + float t = v*(1-(1-f)*s); + switch(i%6){case 0:r=Math.round(255*v);g=Math.round(255*t);b=Math.round(255*p);break;case 1:r=Math.round(255*q);g=Math.round(255*v);b=Math.round(255*p);break;case 2:r=Math.round(255*p);g=Math.round(255*v);b=Math.round(255*t);break;case 3:r=Math.round(255*p);g=Math.round(255*q);b=Math.round(255*v);break;case 4:r=Math.round(255*t);g=Math.round(255*p);b=Math.round(255*v);break;case 5:r=Math.round(255*v);g=Math.round(255*p);b=Math.round(255*q);break;default:r=g=b=0;break;} + return (r<<16)|(g<<8)|b; + } - return switch (layer) { - case 0 -> alphaValue | 0xFFFF99; - case 1 -> alphaValue | 0xFFCC00; - case 2 -> alphaValue | 0xFF9900; - case 3 -> alphaValue | 0xFF6600; - case 4 -> alphaValue | 0xFF3300; - default -> alphaValue | 0xFF0000; - }; + private static int getLayerColor(int layer, float alpha) + { + int alphaValue = (int)(alpha*255)<<24; + final int COLOR_COUNT=20; + final float HUE_START=60f/360f; + final float HUE_END=360f/360f; + int colorIndex = layer % COLOR_COUNT; + float hueStep = (HUE_END-HUE_START)/COLOR_COUNT; + float hue = HUE_START + colorIndex*hueStep; + int loopCount = layer / COLOR_COUNT; + float baseValue = 1.0f; + float valueIncrement = 0.15f*loopCount; + float value = Math.min(1.0f, baseValue+valueIncrement); + if(value>=1.0f && loopCount>0){value=0.6f;hue=HUE_START;} + float saturation=1.0f; + int rgb=hsvToRgb(hue,saturation,value); + return alphaValue|rgb; } public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha, boolean useRottenTextures) { - if (hungerRestored <= 0) - return; - + if(hungerRestored<=0)return; enableAlpha(alpha); - - int modifiedFood = Math.max(0, Math.min(20, foodLevel + hungerRestored)); - - int startFoodBars = Math.max(0, foodLevel / 2); - int endFoodBars = (int) Math.ceil(modifiedFood / 2.0F); - - int iconStartOffset = 16; - int iconSize = 9; - - for (int i = startFoodBars; i < endFoodBars; ++i) + int modifiedFood = Math.max(0,Math.min(20,foodLevel+hungerRestored)); + int startFoodBars = Math.max(0,foodLevel/2); + int endFoodBars = (int)Math.ceil(modifiedFood/2.0F); + int iconStartOffset=16; + int iconSize=9; + for(int i=startFoodBars;i= 1.5f) - { - alphaDir = -1; - } - else if (unclampedFlashAlpha <= -0.5f) - { - alphaDir = 1; - } - flashAlpha = Math.max(0F, Math.min(1F, unclampedFlashAlpha)) * Math.max(0F, Math.min(1F, ModConfig.MAX_HUD_OVERLAY_FLASH_ALPHA.get().floatValue())); + if(event.phase!=TickEvent.Phase.END)return; + unclampedFlashAlpha+=alphaDir*0.125f; + if(unclampedFlashAlpha>=1.5f)alphaDir=-1; + else if(unclampedFlashAlpha<=-0.5f)alphaDir=1; + flashAlpha=Math.max(0f,Math.min(1f,unclampedFlashAlpha))*Math.max(0f,Math.min(1f,ModConfig.MAX_HUD_OVERLAY_FLASH_ALPHA.get().floatValue())); } public static void resetFlash() { - unclampedFlashAlpha = flashAlpha = 0f; - alphaDir = 1; + unclampedFlashAlpha=flashAlpha=0f; + alphaDir=1; } - private static void drawSaturationOverlay(HUDOverlayEvent.Saturation event, Minecraft mc, float saturationGained, float alpha) + private static void drawSaturationOverlay(HUDOverlayEvent.Saturation event,Minecraft mc,float saturationGained,float alpha) { - drawSaturationOverlay(saturationGained, event.saturationLevel, mc, event.guiGraphics, event.x, event.y, alpha); + drawSaturationOverlay(saturationGained,event.saturationLevel,mc,event.guiGraphics,event.x,event.y,alpha); } - private static void drawHungerOverlay(HUDOverlayEvent.HungerRestored event, Minecraft mc, int hunger, float alpha, boolean useRottenTextures) + private static void drawHungerOverlay(HUDOverlayEvent.HungerRestored event,Minecraft mc,int hunger,float alpha,boolean useRottenTextures) { - drawHungerOverlay(hunger, event.currentFoodLevel, mc, event.guiGraphics, event.x, event.y, alpha, useRottenTextures); + drawHungerOverlay(hunger,event.currentFoodLevel,mc,event.guiGraphics,event.x,event.y,alpha,useRottenTextures); } - private static void drawHealthOverlay(HUDOverlayEvent.HealthRestored event, Minecraft mc, float alpha) + private static void drawHealthOverlay(HUDOverlayEvent.HealthRestored event,Minecraft mc,float alpha) { - drawHealthOverlay(mc.player.getHealth(), event.modifiedHealth, mc, event.guiGraphics, event.x, event.y, alpha); + drawHealthOverlay(mc.player.getHealth(),event.modifiedHealth,mc,event.guiGraphics,event.x,event.y,alpha); } - private static void drawExhaustionOverlay(HUDOverlayEvent.Exhaustion event, Minecraft mc, float alpha) + private static void drawExhaustionOverlay(HUDOverlayEvent.Exhaustion event,Minecraft mc,float alpha) { - drawExhaustionOverlay(event.exhaustion, mc, event.guiGraphics, event.x, event.y, alpha); + drawExhaustionOverlay(event.exhaustion,mc,event.guiGraphics,event.x,event.y,alpha); } private static boolean shouldRenderAnyOverlays() @@ -508,139 +387,70 @@ private static boolean shouldRenderAnyOverlays() return ModConfig.SHOW_FOOD_VALUES_OVERLAY.get() || ModConfig.SHOW_SATURATION_OVERLAY.get() || ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get(); } - private static boolean shouldShowEstimatedHealth(ItemStack hoveredStack, FoodValues modifiedFoodValues) + private static boolean shouldShowEstimatedHealth(ItemStack hoveredStack,FoodValues modifiedFoodValues) { - // then configuration cancel the render event - if (!ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get()) - return false; - - Minecraft mc = Minecraft.getInstance(); - Player player = mc.player; - FoodData stats = player.getFoodData(); - - // in the `PEACEFUL` mode, health will restore faster - if (player.level().getDifficulty() == Difficulty.PEACEFUL) - return false; - - // when player has any changes health amount by any case can't show estimated health - // because player will confused how much of restored/damaged healths - if (stats.getFoodLevel() >= 18) - return false; - - if (player.hasEffect(MobEffects.POISON)) - return false; - - if (player.hasEffect(MobEffects.WITHER)) - return false; - - if (player.hasEffect(MobEffects.REGENERATION)) - return false; - + if(!ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get())return false; + Minecraft mc=Minecraft.getInstance(); + Player player=mc.player; + FoodData stats=player.getFoodData(); + if(player.level().getDifficulty()==Difficulty.PEACEFUL)return false; + if(stats.getFoodLevel()>=18)return false; + if(player.hasEffect(MobEffects.POISON))return false; + if(player.hasEffect(MobEffects.WITHER))return false; + if(player.hasEffect(MobEffects.REGENERATION))return false; return true; } - private static void generateHealthBarOffsets(int top, int left, int right, int ticks, Player player) + private static void generateHealthBarOffsets(int top,int left,int right,int ticks,Player player) { - // hard code in `InGameHUD` - random.setSeed((long) (ticks * 312871L)); - - final int preferHealthBars = 10; - final float maxHealth = player.getMaxHealth(); - final float absorptionHealth = (float) Math.ceil(player.getAbsorptionAmount()); - - int healthBars = (int) Math.ceil((maxHealth + absorptionHealth) / 2.0F); - // When maxHealth + absorptionHealth is greater than Integer.INT_MAX, - // Minecraft will disable heart rendering due to a quirk of MathHelper.ceil. - // We have a much lower threshold since there's no reason to get the offsets - // for thousands of hearts. - // Note: Infinite and > INT_MAX absorption has been seen in the wild. - // This will effectively disable rendering whenever health is unexpectedly large. - if (healthBars < 0 || healthBars > 1000) { - healthBarOffsets.setSize(0); - return; - } - - int healthRows = (int) Math.ceil((float) healthBars / 10.0F); - - int healthRowHeight = Math.max(10 - (healthRows - 2), 3); - - boolean shouldAnimatedHealth = false; - - // when some mods using custom render, we need to least provide an option to cancel animation - if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) + random.setSeed((long)(ticks*312871L)); + final int preferHealthBars=10; + final float maxHealth=player.getMaxHealth(); + final float absorptionHealth=(float)Math.ceil(player.getAbsorptionAmount()); + int healthBars=(int)Math.ceil((maxHealth+absorptionHealth)/2.0F); + if(healthBars<0 || healthBars>1000){healthBarOffsets.setSize(0);return;} + int healthRows=(int)Math.ceil((float)healthBars/10.0F); + int healthRowHeight=Math.max(10-(healthRows-2),3); + boolean shouldAnimatedHealth=false; + if(ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) { - // in vanilla health is too low (below 5) will show heartbeat animation - // when regeneration will also show heartbeat animation, but we don't need now - shouldAnimatedHealth = Math.ceil(player.getHealth()) <= 4; + shouldAnimatedHealth=Math.ceil(player.getHealth())<=4; } - - // adjust the size - if (healthBarOffsets.size() != healthBars) - healthBarOffsets.setSize(healthBars); - - // left alignment, multiple rows, reverse - for (int i = healthBars - 1; i >= 0; --i) + if(healthBarOffsets.size()!=healthBars)healthBarOffsets.setSize(healthBars); + for(int i=healthBars-1;i>=0;--i) { - int row = (int) Math.ceil((float) (i + 1) / (float) preferHealthBars) - 1; - int x = left + i % preferHealthBars * 8; - int y = top - row * healthRowHeight; - // apply the animated offset - if (shouldAnimatedHealth) - y += random.nextInt(2); - - // reuse the point object to reduce memory usage - IntPoint point = healthBarOffsets.get(i); - if (point == null) - { - point = new IntPoint(); - healthBarOffsets.set(i, point); - } - - point.x = x - left; - point.y = y - top; + int row=(int)Math.ceil((float)(i+1)/(float)preferHealthBars)-1; + int x=left+i%preferHealthBars*8; + int y=top-row*healthRowHeight; + if(shouldAnimatedHealth)y+=random.nextInt(2); + IntPoint point=healthBarOffsets.get(i); + if(point==null){point=new IntPoint();healthBarOffsets.set(i,point);} + point.x=x-left; + point.y=y-top; } } - private static void generateHungerBarOffsets(int top, int left, int right, int ticks, Player player) + private static void generateHungerBarOffsets(int top,int left,int right,int ticks,Player player) { - final int preferFoodBars = 10; - - boolean shouldAnimatedFood = false; - - // when some mods using custom render, we need to least provide an option to cancel animation - if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) + final int preferFoodBars=10; + boolean shouldAnimatedFood=false; + if(ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) { - FoodData stats = player.getFoodData(); - - // in vanilla saturation level is zero will show hunger animation - float saturationLevel = stats.getSaturationLevel(); - int foodLevel = stats.getFoodLevel(); - shouldAnimatedFood = saturationLevel <= 0.0F && ticks % (foodLevel * 3 + 1) == 0; + FoodData stats=player.getFoodData(); + float saturationLevel=stats.getSaturationLevel(); + int foodLevel=stats.getFoodLevel(); + shouldAnimatedFood=saturationLevel<=0.0F && ticks%(foodLevel*3+1)==0; } - - if (foodBarOffsets.size() != preferFoodBars) - foodBarOffsets.setSize(preferFoodBars); - - // right alignment, single row - for (int i = 0; i < preferFoodBars; ++i) + if(foodBarOffsets.size()!=preferFoodBars)foodBarOffsets.setSize(preferFoodBars); + for(int i=0;i Date: Fri, 30 Jan 2026 14:35:56 -0600 Subject: [PATCH 04/11] Revert "support for saturation uncapped" This reverts commit 8dbb32081048b7234e0f0487355a82bad2de3129. --- .../appleskin/client/HUDOverlayHandler.java | 502 ++++++++++++------ 1 file changed, 346 insertions(+), 156 deletions(-) diff --git a/java/squeek/appleskin/client/HUDOverlayHandler.java b/java/squeek/appleskin/client/HUDOverlayHandler.java index db29490..9175a03 100644 --- a/java/squeek/appleskin/client/HUDOverlayHandler.java +++ b/java/squeek/appleskin/client/HUDOverlayHandler.java @@ -38,8 +38,10 @@ public class HUDOverlayHandler private static float flashAlpha = 0f; private static byte alphaDir = 1; protected static int foodIconsOffset; + public static final Vector healthBarOffsets = new Vector<>(); public static final Vector foodBarOffsets = new Vector<>(); + private static final Random random = new Random(); public static void init() @@ -92,14 +94,19 @@ else if (event.getOverlay() == GuiOverlayManager.findOverlay(PLAYER_HEALTH_ELEME public static void renderExhaustion(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight) { foodIconsOffset = gui.rightHeight; + if (!ModConfig.SHOW_FOOD_EXHAUSTION_UNDERLAY.get()) return; + Minecraft mc = Minecraft.getInstance(); Player player = mc.player; assert player != null; + int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; float exhaustion = player.getFoodData().getExhaustionLevel(); + + // Notify everyone that we should render exhaustion hud overlay HUDOverlayEvent.Exhaustion renderEvent = new HUDOverlayEvent.Exhaustion(exhaustion, right, top, guiGraphics); MinecraftForge.EVENT_BUS.post(renderEvent); if (!renderEvent.isCanceled()) @@ -116,55 +123,78 @@ public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphi { if (!shouldRenderAnyOverlays()) return; + Minecraft mc = Minecraft.getInstance(); Player player = mc.player; assert player != null; FoodData stats = player.getFoodData(); + int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; - int left = mc.getWindow().getGuiScaledWidth() / 2 - 91; - int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; + int left = mc.getWindow().getGuiScaledWidth() / 2 - 91; // left of health bar + int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; // right of food bar + if (type == RenderOverlayType.HEALTH) generateHealthBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); if (type == RenderOverlayType.FOOD) generateHungerBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); + HUDOverlayEvent.Saturation saturationRenderEvent = null; if (type == RenderOverlayType.FOOD) { saturationRenderEvent = new HUDOverlayEvent.Saturation(stats.getSaturationLevel(), right, top, guiGraphics); + + // cancel render overlay event when configuration disabled. if (!ModConfig.SHOW_SATURATION_OVERLAY.get()) saturationRenderEvent.setCanceled(true); + + // notify everyone that we should render saturation hud overlay if (!saturationRenderEvent.isCanceled()) MinecraftForge.EVENT_BUS.post(saturationRenderEvent); + + // the render saturation event maybe cancelled by other mods if (!saturationRenderEvent.isCanceled()) drawSaturationOverlay(saturationRenderEvent, mc, 0, 1f); } + + // try to get the item stack in the player hand ItemStack heldItem = player.getMainHandItem(); if (ModConfig.SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND.get() && !FoodHelper.canConsume(heldItem, player)) heldItem = player.getOffhandItem(); + boolean shouldRenderHeldItemValues = !heldItem.isEmpty() && FoodHelper.canConsume(heldItem, player); if (!shouldRenderHeldItemValues) { resetFlash(); return; } + FoodValues modifiedFoodValues = FoodHelper.getModifiedFoodValues(heldItem, player); FoodValuesEvent foodValuesEvent = new FoodValuesEvent(player, heldItem, FoodHelper.getDefaultFoodValues(heldItem, player), modifiedFoodValues); MinecraftForge.EVENT_BUS.post(foodValuesEvent); modifiedFoodValues = foodValuesEvent.modifiedFoodValues; + if (type == RenderOverlayType.HEALTH) { + // Offsets size is set to zero intentionally to disable rendering when health is infinite. if (healthBarOffsets.size() == 0) return; + if (!shouldShowEstimatedHealth(heldItem, modifiedFoodValues)) return; + float foodHealthIncrement = FoodHelper.getEstimatedHealthIncrement(heldItem, modifiedFoodValues, player); float currentHealth = player.getHealth(); float modifiedHealth = Math.min(currentHealth + foodHealthIncrement, player.getMaxHealth()); + + // only create object when the estimated health is successfully HUDOverlayEvent.HealthRestored healthRenderEvent = null; if (currentHealth < modifiedHealth) healthRenderEvent = new HUDOverlayEvent.HealthRestored(modifiedHealth, heldItem, modifiedFoodValues, left, top, guiGraphics); + + // notify everyone that we should render estimated health hud if (healthRenderEvent != null) MinecraftForge.EVENT_BUS.post(healthRenderEvent); + if (healthRenderEvent != null && !healthRenderEvent.isCanceled()) drawHealthOverlay(healthRenderEvent, mc, flashAlpha); } @@ -172,19 +202,28 @@ else if (type == RenderOverlayType.FOOD) { if (!ModConfig.SHOW_FOOD_VALUES_OVERLAY.get()) return; + + // notify everyone that we should render hunger hud overlay HUDOverlayEvent.HungerRestored renderRenderEvent = new HUDOverlayEvent.HungerRestored(stats.getFoodLevel(), heldItem, modifiedFoodValues, right, top, guiGraphics); MinecraftForge.EVENT_BUS.post(renderRenderEvent); if (renderRenderEvent.isCanceled()) return; + + // calculate the final hunger and saturation int foodHunger = modifiedFoodValues.hunger; float foodSaturationIncrement = modifiedFoodValues.getSaturationIncrement(); + + // restored hunger/saturation overlay while holding food drawHungerOverlay(renderRenderEvent, mc, foodHunger, flashAlpha, FoodHelper.isRotten(heldItem, player)); + + // The render saturation overlay event maybe cancelled by other mods assert saturationRenderEvent != null; if (!saturationRenderEvent.isCanceled()) { int newFoodValue = stats.getFoodLevel() + foodHunger; float newSaturationValue = stats.getSaturationLevel() + foodSaturationIncrement; float saturationGained = newSaturationValue > newFoodValue ? newFoodValue - stats.getSaturationLevel() : foodSaturationIncrement; + // Redraw saturation overlay for gained drawSaturationOverlay(saturationRenderEvent, mc, saturationGained, flashAlpha); } } @@ -192,194 +231,276 @@ else if (type == RenderOverlayType.FOOD) public static void drawSaturationOverlay(float saturationGained, float saturationLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) { - if (saturationLevel + saturationGained < 0) return; + if (saturationLevel + saturationGained < 0) + return; + enableAlpha(alpha); + float modifiedSaturation = saturationLevel + saturationGained; int iconSize = 9; - int currentLayer = (int)(modifiedSaturation / 20); + + int currentLayer = (int) (modifiedSaturation / 20); + float layerSaturation = modifiedSaturation % 20; - if (layerSaturation == 0 && modifiedSaturation > 0) layerSaturation = 20; + if (layerSaturation == 0 && modifiedSaturation > 0) + layerSaturation = 20; + int startSaturationBar = 0; int endSaturationBar = 10; + if (saturationGained != 0) { float currentSaturationInLayer = saturationLevel % 20; - if (currentSaturationInLayer == 0 && saturationLevel > 0) currentSaturationInLayer = 20; - startSaturationBar = (int)(currentSaturationInLayer / 2.0F); + if (currentSaturationInLayer == 0 && saturationLevel > 0) + currentSaturationInLayer = 20; + startSaturationBar = (int) (currentSaturationInLayer / 2.0F); } + for (int i = startSaturationBar; i < endSaturationBar; ++i) { IntPoint offset = foodBarOffsets.get(i); - if (offset == null) continue; + if (offset == null) + continue; + int x = right + offset.x; int y = top + offset.y; + float effectiveSaturationOfBar = (layerSaturation / 2.0F) - i; + int u = 0; - if (effectiveSaturationOfBar >= 1) u = 3 * iconSize; - else if (effectiveSaturationOfBar >= 0.5) u = 2 * iconSize; - else if (effectiveSaturationOfBar > 0.25) u = 1 * iconSize; + if (effectiveSaturationOfBar >= 1) + u = 3 * iconSize; + else if (effectiveSaturationOfBar >= 0.5) + u = 2 * iconSize; + else if (effectiveSaturationOfBar > 0.25) + u = 1 * iconSize; + int currentColor = getLayerColor(currentLayer, alpha); + if (currentLayer > 0) { int previousColor = getLayerColor(currentLayer - 1, alpha); - RenderSystem.setShaderColor(((previousColor >> 16) & 0xFF) / 255f, ((previousColor >> 8) & 0xFF) / 255f, (previousColor & 0xFF) / 255f, ((previousColor >> 24) & 0xFF) / 255f); + + RenderSystem.setShaderColor( + ((previousColor >> 16) & 0xFF) / 255f, + ((previousColor >> 8) & 0xFF) / 255f, + (previousColor & 0xFF) / 255f, + ((previousColor >> 24) & 0xFF) / 255f + ); + guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, 3 * iconSize, 0, iconSize, iconSize); } - RenderSystem.setShaderColor(((currentColor >> 16) & 0xFF) / 255f, ((currentColor >> 8) & 0xFF) / 255f, (currentColor & 0xFF) / 255f, ((currentColor >> 24) & 0xFF) / 255f); + + RenderSystem.setShaderColor( + ((currentColor >> 16) & 0xFF) / 255f, + ((currentColor >> 8) & 0xFF) / 255f, + (currentColor & 0xFF) / 255f, + ((currentColor >> 24) & 0xFF) / 255f + ); + guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, 0, iconSize, iconSize); } - RenderSystem.setShaderColor(1f,1f,1f,1f); + + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); RenderSystem.setShaderTexture(0, TextureHelper.MC_ICONS); disableAlpha(alpha); } - private static int hsvToRgb(float h, float s, float v) - { - int r,g,b; - int i = (int)(h*6); - float f = h*6-i; - float p = v*(1-s); - float q = v*(1-f*s); - float t = v*(1-(1-f)*s); - switch(i%6){case 0:r=Math.round(255*v);g=Math.round(255*t);b=Math.round(255*p);break;case 1:r=Math.round(255*q);g=Math.round(255*v);b=Math.round(255*p);break;case 2:r=Math.round(255*p);g=Math.round(255*v);b=Math.round(255*t);break;case 3:r=Math.round(255*p);g=Math.round(255*q);b=Math.round(255*v);break;case 4:r=Math.round(255*t);g=Math.round(255*p);b=Math.round(255*v);break;case 5:r=Math.round(255*v);g=Math.round(255*p);b=Math.round(255*q);break;default:r=g=b=0;break;} - return (r<<16)|(g<<8)|b; - } - private static int getLayerColor(int layer, float alpha) { - int alphaValue = (int)(alpha*255)<<24; - final int COLOR_COUNT=20; - final float HUE_START=60f/360f; - final float HUE_END=360f/360f; - int colorIndex = layer % COLOR_COUNT; - float hueStep = (HUE_END-HUE_START)/COLOR_COUNT; - float hue = HUE_START + colorIndex*hueStep; - int loopCount = layer / COLOR_COUNT; - float baseValue = 1.0f; - float valueIncrement = 0.15f*loopCount; - float value = Math.min(1.0f, baseValue+valueIncrement); - if(value>=1.0f && loopCount>0){value=0.6f;hue=HUE_START;} - float saturation=1.0f; - int rgb=hsvToRgb(hue,saturation,value); - return alphaValue|rgb; + int alphaValue = (int) (alpha * 255) << 24; + + return switch (layer) { + case 0 -> alphaValue | 0xFFFF99; + case 1 -> alphaValue | 0xFFCC00; + case 2 -> alphaValue | 0xFF9900; + case 3 -> alphaValue | 0xFF6600; + case 4 -> alphaValue | 0xFF3300; + default -> alphaValue | 0xFF0000; + }; } public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha, boolean useRottenTextures) { - if(hungerRestored<=0)return; + if (hungerRestored <= 0) + return; + enableAlpha(alpha); - int modifiedFood = Math.max(0,Math.min(20,foodLevel+hungerRestored)); - int startFoodBars = Math.max(0,foodLevel/2); - int endFoodBars = (int)Math.ceil(modifiedFood/2.0F); - int iconStartOffset=16; - int iconSize=9; - for(int i=startFoodBars;i=1.5f)alphaDir=-1; - else if(unclampedFlashAlpha<=-0.5f)alphaDir=1; - flashAlpha=Math.max(0f,Math.min(1f,unclampedFlashAlpha))*Math.max(0f,Math.min(1f,ModConfig.MAX_HUD_OVERLAY_FLASH_ALPHA.get().floatValue())); + if (event.phase != TickEvent.Phase.END) + return; + + unclampedFlashAlpha += alphaDir * 0.125f; + if (unclampedFlashAlpha >= 1.5f) + { + alphaDir = -1; + } + else if (unclampedFlashAlpha <= -0.5f) + { + alphaDir = 1; + } + flashAlpha = Math.max(0F, Math.min(1F, unclampedFlashAlpha)) * Math.max(0F, Math.min(1F, ModConfig.MAX_HUD_OVERLAY_FLASH_ALPHA.get().floatValue())); } public static void resetFlash() { - unclampedFlashAlpha=flashAlpha=0f; - alphaDir=1; + unclampedFlashAlpha = flashAlpha = 0f; + alphaDir = 1; } - private static void drawSaturationOverlay(HUDOverlayEvent.Saturation event,Minecraft mc,float saturationGained,float alpha) + private static void drawSaturationOverlay(HUDOverlayEvent.Saturation event, Minecraft mc, float saturationGained, float alpha) { - drawSaturationOverlay(saturationGained,event.saturationLevel,mc,event.guiGraphics,event.x,event.y,alpha); + drawSaturationOverlay(saturationGained, event.saturationLevel, mc, event.guiGraphics, event.x, event.y, alpha); } - private static void drawHungerOverlay(HUDOverlayEvent.HungerRestored event,Minecraft mc,int hunger,float alpha,boolean useRottenTextures) + private static void drawHungerOverlay(HUDOverlayEvent.HungerRestored event, Minecraft mc, int hunger, float alpha, boolean useRottenTextures) { - drawHungerOverlay(hunger,event.currentFoodLevel,mc,event.guiGraphics,event.x,event.y,alpha,useRottenTextures); + drawHungerOverlay(hunger, event.currentFoodLevel, mc, event.guiGraphics, event.x, event.y, alpha, useRottenTextures); } - private static void drawHealthOverlay(HUDOverlayEvent.HealthRestored event,Minecraft mc,float alpha) + private static void drawHealthOverlay(HUDOverlayEvent.HealthRestored event, Minecraft mc, float alpha) { - drawHealthOverlay(mc.player.getHealth(),event.modifiedHealth,mc,event.guiGraphics,event.x,event.y,alpha); + drawHealthOverlay(mc.player.getHealth(), event.modifiedHealth, mc, event.guiGraphics, event.x, event.y, alpha); } - private static void drawExhaustionOverlay(HUDOverlayEvent.Exhaustion event,Minecraft mc,float alpha) + private static void drawExhaustionOverlay(HUDOverlayEvent.Exhaustion event, Minecraft mc, float alpha) { - drawExhaustionOverlay(event.exhaustion,mc,event.guiGraphics,event.x,event.y,alpha); + drawExhaustionOverlay(event.exhaustion, mc, event.guiGraphics, event.x, event.y, alpha); } private static boolean shouldRenderAnyOverlays() @@ -387,70 +508,139 @@ private static boolean shouldRenderAnyOverlays() return ModConfig.SHOW_FOOD_VALUES_OVERLAY.get() || ModConfig.SHOW_SATURATION_OVERLAY.get() || ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get(); } - private static boolean shouldShowEstimatedHealth(ItemStack hoveredStack,FoodValues modifiedFoodValues) + private static boolean shouldShowEstimatedHealth(ItemStack hoveredStack, FoodValues modifiedFoodValues) { - if(!ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get())return false; - Minecraft mc=Minecraft.getInstance(); - Player player=mc.player; - FoodData stats=player.getFoodData(); - if(player.level().getDifficulty()==Difficulty.PEACEFUL)return false; - if(stats.getFoodLevel()>=18)return false; - if(player.hasEffect(MobEffects.POISON))return false; - if(player.hasEffect(MobEffects.WITHER))return false; - if(player.hasEffect(MobEffects.REGENERATION))return false; + // then configuration cancel the render event + if (!ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get()) + return false; + + Minecraft mc = Minecraft.getInstance(); + Player player = mc.player; + FoodData stats = player.getFoodData(); + + // in the `PEACEFUL` mode, health will restore faster + if (player.level().getDifficulty() == Difficulty.PEACEFUL) + return false; + + // when player has any changes health amount by any case can't show estimated health + // because player will confused how much of restored/damaged healths + if (stats.getFoodLevel() >= 18) + return false; + + if (player.hasEffect(MobEffects.POISON)) + return false; + + if (player.hasEffect(MobEffects.WITHER)) + return false; + + if (player.hasEffect(MobEffects.REGENERATION)) + return false; + return true; } - private static void generateHealthBarOffsets(int top,int left,int right,int ticks,Player player) + private static void generateHealthBarOffsets(int top, int left, int right, int ticks, Player player) { - random.setSeed((long)(ticks*312871L)); - final int preferHealthBars=10; - final float maxHealth=player.getMaxHealth(); - final float absorptionHealth=(float)Math.ceil(player.getAbsorptionAmount()); - int healthBars=(int)Math.ceil((maxHealth+absorptionHealth)/2.0F); - if(healthBars<0 || healthBars>1000){healthBarOffsets.setSize(0);return;} - int healthRows=(int)Math.ceil((float)healthBars/10.0F); - int healthRowHeight=Math.max(10-(healthRows-2),3); - boolean shouldAnimatedHealth=false; - if(ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) + // hard code in `InGameHUD` + random.setSeed((long) (ticks * 312871L)); + + final int preferHealthBars = 10; + final float maxHealth = player.getMaxHealth(); + final float absorptionHealth = (float) Math.ceil(player.getAbsorptionAmount()); + + int healthBars = (int) Math.ceil((maxHealth + absorptionHealth) / 2.0F); + // When maxHealth + absorptionHealth is greater than Integer.INT_MAX, + // Minecraft will disable heart rendering due to a quirk of MathHelper.ceil. + // We have a much lower threshold since there's no reason to get the offsets + // for thousands of hearts. + // Note: Infinite and > INT_MAX absorption has been seen in the wild. + // This will effectively disable rendering whenever health is unexpectedly large. + if (healthBars < 0 || healthBars > 1000) { + healthBarOffsets.setSize(0); + return; + } + + int healthRows = (int) Math.ceil((float) healthBars / 10.0F); + + int healthRowHeight = Math.max(10 - (healthRows - 2), 3); + + boolean shouldAnimatedHealth = false; + + // when some mods using custom render, we need to least provide an option to cancel animation + if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) { - shouldAnimatedHealth=Math.ceil(player.getHealth())<=4; + // in vanilla health is too low (below 5) will show heartbeat animation + // when regeneration will also show heartbeat animation, but we don't need now + shouldAnimatedHealth = Math.ceil(player.getHealth()) <= 4; } - if(healthBarOffsets.size()!=healthBars)healthBarOffsets.setSize(healthBars); - for(int i=healthBars-1;i>=0;--i) + + // adjust the size + if (healthBarOffsets.size() != healthBars) + healthBarOffsets.setSize(healthBars); + + // left alignment, multiple rows, reverse + for (int i = healthBars - 1; i >= 0; --i) { - int row=(int)Math.ceil((float)(i+1)/(float)preferHealthBars)-1; - int x=left+i%preferHealthBars*8; - int y=top-row*healthRowHeight; - if(shouldAnimatedHealth)y+=random.nextInt(2); - IntPoint point=healthBarOffsets.get(i); - if(point==null){point=new IntPoint();healthBarOffsets.set(i,point);} - point.x=x-left; - point.y=y-top; + int row = (int) Math.ceil((float) (i + 1) / (float) preferHealthBars) - 1; + int x = left + i % preferHealthBars * 8; + int y = top - row * healthRowHeight; + // apply the animated offset + if (shouldAnimatedHealth) + y += random.nextInt(2); + + // reuse the point object to reduce memory usage + IntPoint point = healthBarOffsets.get(i); + if (point == null) + { + point = new IntPoint(); + healthBarOffsets.set(i, point); + } + + point.x = x - left; + point.y = y - top; } } - private static void generateHungerBarOffsets(int top,int left,int right,int ticks,Player player) + private static void generateHungerBarOffsets(int top, int left, int right, int ticks, Player player) { - final int preferFoodBars=10; - boolean shouldAnimatedFood=false; - if(ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) + final int preferFoodBars = 10; + + boolean shouldAnimatedFood = false; + + // when some mods using custom render, we need to least provide an option to cancel animation + if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) { - FoodData stats=player.getFoodData(); - float saturationLevel=stats.getSaturationLevel(); - int foodLevel=stats.getFoodLevel(); - shouldAnimatedFood=saturationLevel<=0.0F && ticks%(foodLevel*3+1)==0; + FoodData stats = player.getFoodData(); + + // in vanilla saturation level is zero will show hunger animation + float saturationLevel = stats.getSaturationLevel(); + int foodLevel = stats.getFoodLevel(); + shouldAnimatedFood = saturationLevel <= 0.0F && ticks % (foodLevel * 3 + 1) == 0; } - if(foodBarOffsets.size()!=preferFoodBars)foodBarOffsets.setSize(preferFoodBars); - for(int i=0;i Date: Sat, 31 Jan 2026 06:08:55 -0600 Subject: [PATCH 05/11] support for higher values than 20 saturation added config that allows you to change the colors and add new ones to higher values added colored HUD to values > 20 better values for displaying decimal place value saturation changed texture of icons.png in the mod assets to have a gray variant for recoloring in multi colored values (subject to further change is ok) --- java/squeek/appleskin/ModConfig.java | 22 +- .../appleskin/client/HUDOverlayHandler.java | 290 +++++++----------- resources/assets/appleskin/textures/icons.png | Bin 480 -> 1344 bytes 3 files changed, 138 insertions(+), 174 deletions(-) diff --git a/java/squeek/appleskin/ModConfig.java b/java/squeek/appleskin/ModConfig.java index c9a2fc7..6184528 100644 --- a/java/squeek/appleskin/ModConfig.java +++ b/java/squeek/appleskin/ModConfig.java @@ -5,6 +5,8 @@ import net.minecraftforge.common.ForgeConfigSpec; import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; public class ModConfig { @@ -89,6 +91,21 @@ public static void init(Path file) private static final String MAX_HUD_OVERLAY_FLASH_ALPHA_COMMENT = "Alpha value of the flashing icons at their most visible point (1.0 = fully opaque, 0.0 = fully transparent)"; + public static final ForgeConfigSpec.ConfigValue> SATURATION_HUD_OVERLAY_COLORS; + public static List SATURATION_HUD_OVERLAY_COLORS_DEFAULT = Arrays.asList( + "#FFD500", + "#FF0000", + "#0000FF", + "#E600FF", + "#FF8B3D", + "#00FFFF", + "#45018F", + "#00FF00" + ); + private static final String SATURATION_HUD_OVERLAY_COLORS_NAME = "saturationHudOverlayColors"; + private static final String SATURATION_HUD_OVERLAY_COLORS_COMMENT = + "The colors to use for the saturation HUD overlay. The colors are in ARGB hex format."; + static { BUILDER.push(CATEGORY_CLIENT); @@ -122,8 +139,11 @@ public static void init(Path file) MAX_HUD_OVERLAY_FLASH_ALPHA = BUILDER .comment(MAX_HUD_OVERLAY_FLASH_ALPHA_COMMENT) .defineInRange(MAX_HUD_OVERLAY_FLASH_ALPHA_NAME, MAX_HUD_OVERLAY_FLASH_ALPHA_DEFAULT, 0D, 1D); + SATURATION_HUD_OVERLAY_COLORS = BUILDER + .comment(SATURATION_HUD_OVERLAY_COLORS_COMMENT) + .defineList(SATURATION_HUD_OVERLAY_COLORS_NAME, SATURATION_HUD_OVERLAY_COLORS_DEFAULT, o -> o instanceof String); BUILDER.pop(); } public static final ForgeConfigSpec SPEC = BUILDER.build(); -} \ No newline at end of file +} diff --git a/java/squeek/appleskin/client/HUDOverlayHandler.java b/java/squeek/appleskin/client/HUDOverlayHandler.java index 9175a03..953b978 100644 --- a/java/squeek/appleskin/client/HUDOverlayHandler.java +++ b/java/squeek/appleskin/client/HUDOverlayHandler.java @@ -3,7 +3,9 @@ import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FastColor; import net.minecraft.world.Difficulty; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.LivingEntity; @@ -19,6 +21,8 @@ import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import org.lwjgl.opengl.GL11; +import org.slf4j.Logger; +import squeek.appleskin.AppleSkin; import squeek.appleskin.ModConfig; import squeek.appleskin.api.event.FoodValuesEvent; import squeek.appleskin.api.event.HUDOverlayEvent; @@ -28,12 +32,13 @@ import squeek.appleskin.helpers.TextureHelper; import squeek.appleskin.util.IntPoint; +import java.util.ArrayList; +import java.util.List; import java.util.Random; import java.util.Vector; @OnlyIn(Dist.CLIENT) -public class HUDOverlayHandler -{ +public class HUDOverlayHandler { private static float unclampedFlashAlpha = 0f; private static float flashAlpha = 0f; private static byte alphaDir = 1; @@ -44,8 +49,7 @@ public class HUDOverlayHandler private static final Random random = new Random(); - public static void init() - { + public static void init() { MinecraftForge.EVENT_BUS.register(new HUDOverlayHandler()); } @@ -53,46 +57,36 @@ public static void init() static ResourceLocation PLAYER_HEALTH_ELEMENT = new ResourceLocation("minecraft", "player_health"); @SubscribeEvent - public void onRenderGuiOverlayPre(RenderGuiOverlayEvent.Pre event) - { - if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) - { + public void onRenderGuiOverlayPre(RenderGuiOverlayEvent.Pre event) { + if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) { Minecraft mc = Minecraft.getInstance(); ForgeGui gui = (ForgeGui) mc.gui; boolean isMounted = mc.player.getVehicle() instanceof LivingEntity; - if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) - { + if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) { renderExhaustion(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight()); } } } @SubscribeEvent - public void onRenderGuiOverlayPost(RenderGuiOverlayEvent.Post event) - { - if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) - { + public void onRenderGuiOverlayPost(RenderGuiOverlayEvent.Post event) { + if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) { Minecraft mc = Minecraft.getInstance(); ForgeGui gui = (ForgeGui) mc.gui; boolean isMounted = mc.player.getVehicle() instanceof LivingEntity; - if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) - { + if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) { renderFoodOrHealthOverlay(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight(), RenderOverlayType.FOOD); } - } - else if (event.getOverlay() == GuiOverlayManager.findOverlay(PLAYER_HEALTH_ELEMENT)) - { + } else if (event.getOverlay() == GuiOverlayManager.findOverlay(PLAYER_HEALTH_ELEMENT)) { Minecraft mc = Minecraft.getInstance(); ForgeGui gui = (ForgeGui) mc.gui; - if (!mc.options.hideGui && gui.shouldDrawSurvivalElements()) - { + if (!mc.options.hideGui && gui.shouldDrawSurvivalElements()) { renderFoodOrHealthOverlay(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight(), RenderOverlayType.HEALTH); } } } - public static void renderExhaustion(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight) - { + public static void renderExhaustion(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight) { foodIconsOffset = gui.rightHeight; if (!ModConfig.SHOW_FOOD_EXHAUSTION_UNDERLAY.get()) @@ -113,14 +107,12 @@ public static void renderExhaustion(ForgeGui gui, GuiGraphics guiGraphics, float drawExhaustionOverlay(renderEvent, mc, 1f); } - enum RenderOverlayType - { + enum RenderOverlayType { HEALTH, FOOD, } - public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight, RenderOverlayType type) - { + public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight, RenderOverlayType type) { if (!shouldRenderAnyOverlays()) return; @@ -139,8 +131,7 @@ public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphi generateHungerBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); HUDOverlayEvent.Saturation saturationRenderEvent = null; - if (type == RenderOverlayType.FOOD) - { + if (type == RenderOverlayType.FOOD) { saturationRenderEvent = new HUDOverlayEvent.Saturation(stats.getSaturationLevel(), right, top, guiGraphics); // cancel render overlay event when configuration disabled. @@ -162,8 +153,7 @@ public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphi heldItem = player.getOffhandItem(); boolean shouldRenderHeldItemValues = !heldItem.isEmpty() && FoodHelper.canConsume(heldItem, player); - if (!shouldRenderHeldItemValues) - { + if (!shouldRenderHeldItemValues) { resetFlash(); return; } @@ -173,10 +163,9 @@ public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphi MinecraftForge.EVENT_BUS.post(foodValuesEvent); modifiedFoodValues = foodValuesEvent.modifiedFoodValues; - if (type == RenderOverlayType.HEALTH) - { + if (type == RenderOverlayType.HEALTH) { // Offsets size is set to zero intentionally to disable rendering when health is infinite. - if (healthBarOffsets.size() == 0) + if (healthBarOffsets.isEmpty()) return; if (!shouldShowEstimatedHealth(heldItem, modifiedFoodValues)) @@ -197,9 +186,7 @@ public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphi if (healthRenderEvent != null && !healthRenderEvent.isCanceled()) drawHealthOverlay(healthRenderEvent, mc, flashAlpha); - } - else if (type == RenderOverlayType.FOOD) - { + } else if (type == RenderOverlayType.FOOD) { if (!ModConfig.SHOW_FOOD_VALUES_OVERLAY.get()) return; @@ -217,9 +204,7 @@ else if (type == RenderOverlayType.FOOD) drawHungerOverlay(renderRenderEvent, mc, foodHunger, flashAlpha, FoodHelper.isRotten(heldItem, player)); // The render saturation overlay event maybe cancelled by other mods - assert saturationRenderEvent != null; - if (!saturationRenderEvent.isCanceled()) - { + if (!saturationRenderEvent.isCanceled()) { int newFoodValue = stats.getFoodLevel() + foodHunger; float newSaturationValue = stats.getSaturationLevel() + foodSaturationIncrement; float saturationGained = newSaturationValue > newFoodValue ? newFoodValue - stats.getSaturationLevel() : foodSaturationIncrement; @@ -229,99 +214,84 @@ else if (type == RenderOverlayType.FOOD) } } - public static void drawSaturationOverlay(float saturationGained, float saturationLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) - { - if (saturationLevel + saturationGained < 0) - return; - - enableAlpha(alpha); + public static void drawSaturationOverlay(float saturationGained, float saturationLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) { + float totalSaturation = saturationLevel + saturationGained; + if (totalSaturation <= 0) return; - float modifiedSaturation = saturationLevel + saturationGained; int iconSize = 9; - - int currentLayer = (int) (modifiedSaturation / 20); - - float layerSaturation = modifiedSaturation % 20; - if (layerSaturation == 0 && modifiedSaturation > 0) - layerSaturation = 20; - - int startSaturationBar = 0; - int endSaturationBar = 10; - - if (saturationGained != 0) - { - float currentSaturationInLayer = saturationLevel % 20; - if (currentSaturationInLayer == 0 && saturationLevel > 0) - currentSaturationInLayer = 20; - startSaturationBar = (int) (currentSaturationInLayer / 2.0F); + int maxBars = 10; + float saturationPerLayer = 20f; + float saturationPerBar = saturationPerLayer / maxBars; + + int[] colors = ModConfig.SATURATION_HUD_OVERLAY_COLORS.get() + .stream() + .mapToInt(color -> { + try { + return Integer.decode(color); + } catch (NumberFormatException e) { + AppleSkin.Log.warn("Invalid color value in config: {}", color); + return 0xFFD500; // fallback (yellow) + } + }) + .toArray(); + + + int fullLayers = (int) (totalSaturation / saturationPerLayer); + float remainder = totalSaturation % saturationPerLayer; + + // Draw all full layers + for (int layer = 0; layer < fullLayers; layer++) { + int color = (int) alpha | colors[layer % colors.length]; + for (int i = 0; i < maxBars; i++) { + IntPoint offset = foodBarOffsets.get(i); + if (offset == null) continue; + + int x = right + offset.x; + int y = top + offset.y; + drawIconTinted(guiGraphics, x, y, 7 * iconSize, iconSize, iconSize, color); + } } - for (int i = startSaturationBar; i < endSaturationBar; ++i) - { - IntPoint offset = foodBarOffsets.get(i); - if (offset == null) - continue; - - int x = right + offset.x; - int y = top + offset.y; - - float effectiveSaturationOfBar = (layerSaturation / 2.0F) - i; - - int u = 0; - if (effectiveSaturationOfBar >= 1) - u = 3 * iconSize; - else if (effectiveSaturationOfBar >= 0.5) - u = 2 * iconSize; - else if (effectiveSaturationOfBar > 0.25) - u = 1 * iconSize; - - int currentColor = getLayerColor(currentLayer, alpha); - - if (currentLayer > 0) - { - int previousColor = getLayerColor(currentLayer - 1, alpha); - - RenderSystem.setShaderColor( - ((previousColor >> 16) & 0xFF) / 255f, - ((previousColor >> 8) & 0xFF) / 255f, - (previousColor & 0xFF) / 255f, - ((previousColor >> 24) & 0xFF) / 255f - ); - - guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, 3 * iconSize, 0, iconSize, iconSize); + // Draw partial layer if there is remaining saturation + if (remainder > 0) { + int color = (int) alpha | colors[fullLayers % colors.length]; + int fullBars = (int) (remainder / saturationPerBar); + float lastBarFraction = remainder % saturationPerBar; + + for (int i = 0; i < fullBars; i++) { + IntPoint offset = foodBarOffsets.get(i); + if (offset == null) continue; + int x = right + offset.x; + int y = top + offset.y; + drawIconTinted(guiGraphics, x, y, 7 * iconSize, iconSize, iconSize, color); } - RenderSystem.setShaderColor( - ((currentColor >> 16) & 0xFF) / 255f, - ((currentColor >> 8) & 0xFF) / 255f, - (currentColor & 0xFF) / 255f, - ((currentColor >> 24) & 0xFF) / 255f - ); - - guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, 0, iconSize, iconSize); + if (lastBarFraction > 0) { + IntPoint offset = foodBarOffsets.get(fullBars); + if (offset != null) { + int x = right + offset.x; + int y = top + offset.y; + int u = lastBarFraction >= 1.5f ? 6 * iconSize + : lastBarFraction >= 1f ? 5 * iconSize + : lastBarFraction > .5f ? 4 * iconSize + : 0; + drawIconTinted(guiGraphics, x, y, u, iconSize, iconSize, color); + } + } } - - RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); - RenderSystem.setShaderTexture(0, TextureHelper.MC_ICONS); - disableAlpha(alpha); } - private static int getLayerColor(int layer, float alpha) - { - int alphaValue = (int) (alpha * 255) << 24; - - return switch (layer) { - case 0 -> alphaValue | 0xFFFF99; - case 1 -> alphaValue | 0xFFCC00; - case 2 -> alphaValue | 0xFF9900; - case 3 -> alphaValue | 0xFF6600; - case 4 -> alphaValue | 0xFF3300; - default -> alphaValue | 0xFF0000; - }; + private static void drawIconTinted(GuiGraphics guiGraphics, int x, int y, int u, int width, int height, int color) { + float r = FastColor.ARGB32.red(color) / 255f; + float g = FastColor.ARGB32.green(color) / 255f; + float b = FastColor.ARGB32.blue(color) / 255f; + float a = FastColor.ARGB32.alpha(color) / 255f; + + guiGraphics.setColor(r, g, b, a); + guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, 0, width, height); } - public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha, boolean useRottenTextures) - { + public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha, boolean useRottenTextures) { if (hungerRestored <= 0) return; @@ -335,8 +305,7 @@ public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraf int iconStartOffset = 16; int iconSize = 9; - for (int i = startFoodBars; i < endFoodBars; ++i) - { + for (int i = startFoodBars; i < endFoodBars; ++i) { // gets the offset that needs to be render of icon IntPoint offset = foodBarOffsets.get(i); if (offset == null) @@ -351,8 +320,7 @@ public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraf int ub = iconStartOffset + 1 * iconSize; // relocation to rotten food - if (useRottenTextures) - { + if (useRottenTextures) { u += 4 * iconSize; ub += 12 * iconSize; } @@ -372,8 +340,7 @@ public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraf disableAlpha(alpha); } - public static void drawHealthOverlay(float health, float modifiedHealth, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) - { + public static void drawHealthOverlay(float health, float modifiedHealth, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) { if (modifiedHealth <= health) return; @@ -388,8 +355,7 @@ public static void drawHealthOverlay(float health, float modifiedHealth, Minecra int iconStartOffset = 16; int iconSize = 9; - for (int i = startHealthBars; i < endHealthBars; ++i) - { + for (int i = startHealthBars; i < endHealthBars; ++i) { // gets the offset that needs to be render of icon IntPoint offset = healthBarOffsets.get(i); if (offset == null) @@ -429,8 +395,7 @@ public static void drawHealthOverlay(float health, float modifiedHealth, Minecra disableAlpha(alpha); } - public static void drawExhaustionOverlay(float exhaustion, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) - { + public static void drawExhaustionOverlay(float exhaustion, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) { float maxExhaustion = HungerHelper.getMaxExhaustion(mc.player); // clamp between 0 and 1 float ratio = Math.min(1, Math.max(0, exhaustion / maxExhaustion)); @@ -446,70 +411,57 @@ public static void drawExhaustionOverlay(float exhaustion, Minecraft mc, GuiGrap } - public static void enableAlpha(float alpha) - { + public static void enableAlpha(float alpha) { RenderSystem.enableBlend(); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); } - public static void disableAlpha(float alpha) - { + public static void disableAlpha(float alpha) { RenderSystem.disableBlend(); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); } @SubscribeEvent - public void onClientTick(TickEvent.ClientTickEvent event) - { + public void onClientTick(TickEvent.ClientTickEvent event) { if (event.phase != TickEvent.Phase.END) return; unclampedFlashAlpha += alphaDir * 0.125f; - if (unclampedFlashAlpha >= 1.5f) - { + if (unclampedFlashAlpha >= 1.5f) { alphaDir = -1; - } - else if (unclampedFlashAlpha <= -0.5f) - { + } else if (unclampedFlashAlpha <= -0.5f) { alphaDir = 1; } flashAlpha = Math.max(0F, Math.min(1F, unclampedFlashAlpha)) * Math.max(0F, Math.min(1F, ModConfig.MAX_HUD_OVERLAY_FLASH_ALPHA.get().floatValue())); } - public static void resetFlash() - { + public static void resetFlash() { unclampedFlashAlpha = flashAlpha = 0f; alphaDir = 1; } - private static void drawSaturationOverlay(HUDOverlayEvent.Saturation event, Minecraft mc, float saturationGained, float alpha) - { + private static void drawSaturationOverlay(HUDOverlayEvent.Saturation event, Minecraft mc, float saturationGained, float alpha) { drawSaturationOverlay(saturationGained, event.saturationLevel, mc, event.guiGraphics, event.x, event.y, alpha); } - private static void drawHungerOverlay(HUDOverlayEvent.HungerRestored event, Minecraft mc, int hunger, float alpha, boolean useRottenTextures) - { + private static void drawHungerOverlay(HUDOverlayEvent.HungerRestored event, Minecraft mc, int hunger, float alpha, boolean useRottenTextures) { drawHungerOverlay(hunger, event.currentFoodLevel, mc, event.guiGraphics, event.x, event.y, alpha, useRottenTextures); } - private static void drawHealthOverlay(HUDOverlayEvent.HealthRestored event, Minecraft mc, float alpha) - { + private static void drawHealthOverlay(HUDOverlayEvent.HealthRestored event, Minecraft mc, float alpha) { drawHealthOverlay(mc.player.getHealth(), event.modifiedHealth, mc, event.guiGraphics, event.x, event.y, alpha); } - private static void drawExhaustionOverlay(HUDOverlayEvent.Exhaustion event, Minecraft mc, float alpha) - { + private static void drawExhaustionOverlay(HUDOverlayEvent.Exhaustion event, Minecraft mc, float alpha) { drawExhaustionOverlay(event.exhaustion, mc, event.guiGraphics, event.x, event.y, alpha); } - private static boolean shouldRenderAnyOverlays() - { + private static boolean shouldRenderAnyOverlays() { return ModConfig.SHOW_FOOD_VALUES_OVERLAY.get() || ModConfig.SHOW_SATURATION_OVERLAY.get() || ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get(); } - private static boolean shouldShowEstimatedHealth(ItemStack hoveredStack, FoodValues modifiedFoodValues) - { + private static boolean shouldShowEstimatedHealth(ItemStack hoveredStack, FoodValues modifiedFoodValues) { // then configuration cancel the render event if (!ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get()) return false; @@ -539,8 +491,7 @@ private static boolean shouldShowEstimatedHealth(ItemStack hoveredStack, FoodVal return true; } - private static void generateHealthBarOffsets(int top, int left, int right, int ticks, Player player) - { + private static void generateHealthBarOffsets(int top, int left, int right, int ticks, Player player) { // hard code in `InGameHUD` random.setSeed((long) (ticks * 312871L)); @@ -567,8 +518,7 @@ private static void generateHealthBarOffsets(int top, int left, int right, int t boolean shouldAnimatedHealth = false; // when some mods using custom render, we need to least provide an option to cancel animation - if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) - { + if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) { // in vanilla health is too low (below 5) will show heartbeat animation // when regeneration will also show heartbeat animation, but we don't need now shouldAnimatedHealth = Math.ceil(player.getHealth()) <= 4; @@ -579,8 +529,7 @@ private static void generateHealthBarOffsets(int top, int left, int right, int t healthBarOffsets.setSize(healthBars); // left alignment, multiple rows, reverse - for (int i = healthBars - 1; i >= 0; --i) - { + for (int i = healthBars - 1; i >= 0; --i) { int row = (int) Math.ceil((float) (i + 1) / (float) preferHealthBars) - 1; int x = left + i % preferHealthBars * 8; int y = top - row * healthRowHeight; @@ -590,8 +539,7 @@ private static void generateHealthBarOffsets(int top, int left, int right, int t // reuse the point object to reduce memory usage IntPoint point = healthBarOffsets.get(i); - if (point == null) - { + if (point == null) { point = new IntPoint(); healthBarOffsets.set(i, point); } @@ -601,15 +549,13 @@ private static void generateHealthBarOffsets(int top, int left, int right, int t } } - private static void generateHungerBarOffsets(int top, int left, int right, int ticks, Player player) - { + private static void generateHungerBarOffsets(int top, int left, int right, int ticks, Player player) { final int preferFoodBars = 10; boolean shouldAnimatedFood = false; // when some mods using custom render, we need to least provide an option to cancel animation - if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) - { + if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) { FoodData stats = player.getFoodData(); // in vanilla saturation level is zero will show hunger animation @@ -622,8 +568,7 @@ private static void generateHungerBarOffsets(int top, int left, int right, int t foodBarOffsets.setSize(preferFoodBars); // right alignment, single row - for (int i = 0; i < preferFoodBars; ++i) - { + for (int i = 0; i < preferFoodBars; ++i) { int x = right - i * 8 - 9; int y = top; @@ -633,8 +578,7 @@ private static void generateHungerBarOffsets(int top, int left, int right, int t // reuse the point object to reduce memory usage IntPoint point = foodBarOffsets.get(i); - if (point == null) - { + if (point == null) { point = new IntPoint(); foodBarOffsets.set(i, point); } diff --git a/resources/assets/appleskin/textures/icons.png b/resources/assets/appleskin/textures/icons.png index 5844b03496b848669afddfb30a7d1b3c86c829e3..7cb2351b8c77a8d3be7b8b3db764c78e3e67d06c 100644 GIT binary patch literal 1344 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzHWd|i0jKsNoUTSxqHZQ_wL*AN9$JwrW1!!8c7b3jEqQX@RmJUz7-IDi~h1}R2Xuz$RO zJZUH!=tTw%MrN=$6Oe7l$ix70R}>IuwzGi6bAW6RNC09`c))0MTNxOD!8C!Lfd#0> zz{uEuaRJ0!kRH|r5VNKM*&x6KG>HkUGRV>b$b#xJG%x_kvYB2f-qc@Y4K#6vr;B4q zMcmt&cPA|}kYK$Tn8^C^uY9?-y~`zoX`%0Yi~OQZf4UtTXlj z5nGOnDg4{DUw+w-*Z=A=`d{gND39iM-hXBL!D`z(@o9F^pVSxBTv7WWv*As9#e_3| zI(F-L-}$Ag8)?R6&+(RfiRGU;^8Fi5Z@6=+s9M%EMuC0Bxf;o9NzZnK>jYcA(giuf z=J`*-cV9n?OkWxG?5CmmYz5Yc`QEn*9W^1#l*Ys73T65qs&)%1)K96 z_iN82#~jPb{1tiJ{YZWL@rqsNr{DX}vxMR0?*7Y5KXX60emmix-Ga||=ig&{eC>Va z%fG*l=N4$5{$=%QufrbRw&nA_Zko3EA9G7|Y{e&A{ldsgo7Q<|-C>(vv1{WRpz8Au zTodN~x$a#jbMGf>!H=R4KK2CxhZ!}l6fkg^$}~9fj&cV&H2yJeKK@zs(C*`jp#19T L>gTe~DWM4fFaqN) literal 480 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58CaNs)Vi3hp+HJ8z$e5NNNZ?laB*?17GgNG zkYRos=WIcSyN4Vfh%>yrl=NGYK{EE<0iYtrk|4ie28U-i(tzw-PZ!6Kid%0lTl*aj z5NLgPP+v~;;Isujo141#G)AW|`aQgt8PqP@B75Od;AXErSS7?{runRHYLj_Umz7_R|GMD^#u@ z&+J~RVxDoTPE&55$^BXEqT=Oh>b0RYEWYddyZY~_JPG#Hj}hB&vA8jC>8FSVr@kIf zPQU8-bit%M63cX_O}gV4-0SW6`kB|aXu0*NMvqx57~>wS-`lYE?55&7=O#vdOS!gA zx7w`Q+HZeQcKGX@s~Sm$?w{08gk8Lq);HIQd&#!Uq;0ovT{sf7vC?b*LA|J(F^hcn zA4mxMHvL9K)@jLmJLf&@zWMv)Tb^v!6(V}+5^L5zzj56zJ)-^1*3IkquI?+gJbXQ& u($+n6_ngG^Y3-J)3ay-hK}r<(e};d;J!4VkW%d_A>OEckT-G@yGywo82f}6m From 1988267c84d7be45bda2830c9d2cc952272a9fb7 Mon Sep 17 00:00:00 2001 From: Mysticpasta1 Date: Sat, 31 Jan 2026 06:38:51 -0600 Subject: [PATCH 06/11] bump version to 2.6.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index bec67ba..a233583 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,4 +11,4 @@ org.gradle.daemon=false # Mod Properties maven_group = squeek.appleskin archives_base_name = appleskin-forge -mod_version = 2.5.1 +mod_version = 2.6.0 From 72f494c46cac70529abcf2449fb91b3a571ad7fe Mon Sep 17 00:00:00 2001 From: Mysticpasta1 Date: Sun, 1 Feb 2026 20:57:30 -0600 Subject: [PATCH 07/11] add .editorconfig for project and fix codestyling of HUDOverlayHandler --- .editorconfig | 272 ++++ .../appleskin/client/HUDOverlayHandler.java | 1119 +++++++++-------- 2 files changed, 842 insertions(+), 549 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b4885be --- /dev/null +++ b/.editorconfig @@ -0,0 +1,272 @@ +root = true + +[*] +charset = utf-8 + +[*.java] +indent_style = tab +ij_continuation_indent_size = 4 +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_deconstruction_list_components = true +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = true +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_align_types_in_multi_catch = true +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_field_with_annotations = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = next_line +ij_java_block_comment_add_space = false +ij_java_block_comment_at_first_column = true +ij_java_builder_methods = +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = off +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = true +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = next_line +ij_java_class_count_to_use_import_on_demand = 5 +ij_java_class_names_in_javadoc = 1 +ij_java_deconstruction_list_wrap = normal +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_not_wrap_after_single_annotation_in_parameter = false +ij_java_do_while_brace_force = never +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = true +ij_java_enum_constants_wrap = off +ij_java_enum_field_annotation_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = split_into_lines +ij_java_field_name_prefix = +ij_java_field_name_suffix = +ij_java_finally_on_new_line = true +ij_java_for_brace_force = never +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_generate_use_type_annotation_before_type = true +ij_java_if_brace_force = never +ij_java_imports_layout = *,|,javax.**,java.**,|,$* +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_builder_methods_indents = false +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_add_space_on_reformat = false +ij_java_line_comment_at_first_column = true +ij_java_local_variable_name_prefix = +ij_java_local_variable_name_suffix = +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = next_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = off +ij_java_modifier_list_wrap = false +ij_java_multi_catch_types_wrap = normal +ij_java_names_count_to_use_import_on_demand = 3 +ij_java_new_line_after_lparen_in_annotation = false +ij_java_new_line_after_lparen_in_deconstruction_pattern = true +ij_java_new_line_after_lparen_in_record_header = false +ij_java_new_line_when_body_is_presented = false +ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.* +ij_java_parameter_annotation_wrap = off +ij_java_parameter_name_prefix = +ij_java_parameter_name_suffix = +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_annotations = +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_annotation = false +ij_java_rparen_on_new_line_in_deconstruction_pattern = true +ij_java_rparen_on_new_line_in_record_header = false +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_deconstruction_list = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_annotation_eq = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_inside_block_braces_when_body_is_present = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_deconstruction_list = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_static_field_name_prefix = +ij_java_static_field_name_suffix = +ij_java_subclass_name_prefix = +ij_java_subclass_name_suffix = Impl +ij_java_switch_expressions_wrap = normal +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_prefix = +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = off +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = never +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false +ij_java_wrap_semicolon_after_call_chain = false \ No newline at end of file diff --git a/java/squeek/appleskin/client/HUDOverlayHandler.java b/java/squeek/appleskin/client/HUDOverlayHandler.java index 953b978..71ac3b5 100644 --- a/java/squeek/appleskin/client/HUDOverlayHandler.java +++ b/java/squeek/appleskin/client/HUDOverlayHandler.java @@ -38,553 +38,574 @@ import java.util.Vector; @OnlyIn(Dist.CLIENT) -public class HUDOverlayHandler { - private static float unclampedFlashAlpha = 0f; - private static float flashAlpha = 0f; - private static byte alphaDir = 1; - protected static int foodIconsOffset; - - public static final Vector healthBarOffsets = new Vector<>(); - public static final Vector foodBarOffsets = new Vector<>(); - - private static final Random random = new Random(); - - public static void init() { - MinecraftForge.EVENT_BUS.register(new HUDOverlayHandler()); - } - - static ResourceLocation FOOD_LEVEL_ELEMENT = new ResourceLocation("minecraft", "food_level"); - static ResourceLocation PLAYER_HEALTH_ELEMENT = new ResourceLocation("minecraft", "player_health"); - - @SubscribeEvent - public void onRenderGuiOverlayPre(RenderGuiOverlayEvent.Pre event) { - if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) { - Minecraft mc = Minecraft.getInstance(); - ForgeGui gui = (ForgeGui) mc.gui; - boolean isMounted = mc.player.getVehicle() instanceof LivingEntity; - if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) { - renderExhaustion(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight()); - } - } - } - - @SubscribeEvent - public void onRenderGuiOverlayPost(RenderGuiOverlayEvent.Post event) { - if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) { - Minecraft mc = Minecraft.getInstance(); - ForgeGui gui = (ForgeGui) mc.gui; - boolean isMounted = mc.player.getVehicle() instanceof LivingEntity; - if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) { - renderFoodOrHealthOverlay(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight(), RenderOverlayType.FOOD); - } - } else if (event.getOverlay() == GuiOverlayManager.findOverlay(PLAYER_HEALTH_ELEMENT)) { - Minecraft mc = Minecraft.getInstance(); - ForgeGui gui = (ForgeGui) mc.gui; - if (!mc.options.hideGui && gui.shouldDrawSurvivalElements()) { - renderFoodOrHealthOverlay(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight(), RenderOverlayType.HEALTH); - } - } - } - - public static void renderExhaustion(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight) { - foodIconsOffset = gui.rightHeight; - - if (!ModConfig.SHOW_FOOD_EXHAUSTION_UNDERLAY.get()) - return; - - Minecraft mc = Minecraft.getInstance(); - Player player = mc.player; - assert player != null; - - int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; - int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; - float exhaustion = player.getFoodData().getExhaustionLevel(); - - // Notify everyone that we should render exhaustion hud overlay - HUDOverlayEvent.Exhaustion renderEvent = new HUDOverlayEvent.Exhaustion(exhaustion, right, top, guiGraphics); - MinecraftForge.EVENT_BUS.post(renderEvent); - if (!renderEvent.isCanceled()) - drawExhaustionOverlay(renderEvent, mc, 1f); - } - - enum RenderOverlayType { - HEALTH, - FOOD, - } - - public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight, RenderOverlayType type) { - if (!shouldRenderAnyOverlays()) - return; - - Minecraft mc = Minecraft.getInstance(); - Player player = mc.player; - assert player != null; - FoodData stats = player.getFoodData(); - - int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; - int left = mc.getWindow().getGuiScaledWidth() / 2 - 91; // left of health bar - int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; // right of food bar - - if (type == RenderOverlayType.HEALTH) - generateHealthBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); - if (type == RenderOverlayType.FOOD) - generateHungerBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); - - HUDOverlayEvent.Saturation saturationRenderEvent = null; - if (type == RenderOverlayType.FOOD) { - saturationRenderEvent = new HUDOverlayEvent.Saturation(stats.getSaturationLevel(), right, top, guiGraphics); - - // cancel render overlay event when configuration disabled. - if (!ModConfig.SHOW_SATURATION_OVERLAY.get()) - saturationRenderEvent.setCanceled(true); - - // notify everyone that we should render saturation hud overlay - if (!saturationRenderEvent.isCanceled()) - MinecraftForge.EVENT_BUS.post(saturationRenderEvent); - - // the render saturation event maybe cancelled by other mods - if (!saturationRenderEvent.isCanceled()) - drawSaturationOverlay(saturationRenderEvent, mc, 0, 1f); - } - - // try to get the item stack in the player hand - ItemStack heldItem = player.getMainHandItem(); - if (ModConfig.SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND.get() && !FoodHelper.canConsume(heldItem, player)) - heldItem = player.getOffhandItem(); - - boolean shouldRenderHeldItemValues = !heldItem.isEmpty() && FoodHelper.canConsume(heldItem, player); - if (!shouldRenderHeldItemValues) { - resetFlash(); - return; - } - - FoodValues modifiedFoodValues = FoodHelper.getModifiedFoodValues(heldItem, player); - FoodValuesEvent foodValuesEvent = new FoodValuesEvent(player, heldItem, FoodHelper.getDefaultFoodValues(heldItem, player), modifiedFoodValues); - MinecraftForge.EVENT_BUS.post(foodValuesEvent); - modifiedFoodValues = foodValuesEvent.modifiedFoodValues; - - if (type == RenderOverlayType.HEALTH) { - // Offsets size is set to zero intentionally to disable rendering when health is infinite. - if (healthBarOffsets.isEmpty()) - return; - - if (!shouldShowEstimatedHealth(heldItem, modifiedFoodValues)) - return; - - float foodHealthIncrement = FoodHelper.getEstimatedHealthIncrement(heldItem, modifiedFoodValues, player); - float currentHealth = player.getHealth(); - float modifiedHealth = Math.min(currentHealth + foodHealthIncrement, player.getMaxHealth()); - - // only create object when the estimated health is successfully - HUDOverlayEvent.HealthRestored healthRenderEvent = null; - if (currentHealth < modifiedHealth) - healthRenderEvent = new HUDOverlayEvent.HealthRestored(modifiedHealth, heldItem, modifiedFoodValues, left, top, guiGraphics); - - // notify everyone that we should render estimated health hud - if (healthRenderEvent != null) - MinecraftForge.EVENT_BUS.post(healthRenderEvent); - - if (healthRenderEvent != null && !healthRenderEvent.isCanceled()) - drawHealthOverlay(healthRenderEvent, mc, flashAlpha); - } else if (type == RenderOverlayType.FOOD) { - if (!ModConfig.SHOW_FOOD_VALUES_OVERLAY.get()) - return; - - // notify everyone that we should render hunger hud overlay - HUDOverlayEvent.HungerRestored renderRenderEvent = new HUDOverlayEvent.HungerRestored(stats.getFoodLevel(), heldItem, modifiedFoodValues, right, top, guiGraphics); - MinecraftForge.EVENT_BUS.post(renderRenderEvent); - if (renderRenderEvent.isCanceled()) - return; - - // calculate the final hunger and saturation - int foodHunger = modifiedFoodValues.hunger; - float foodSaturationIncrement = modifiedFoodValues.getSaturationIncrement(); - - // restored hunger/saturation overlay while holding food - drawHungerOverlay(renderRenderEvent, mc, foodHunger, flashAlpha, FoodHelper.isRotten(heldItem, player)); - - // The render saturation overlay event maybe cancelled by other mods - if (!saturationRenderEvent.isCanceled()) { - int newFoodValue = stats.getFoodLevel() + foodHunger; - float newSaturationValue = stats.getSaturationLevel() + foodSaturationIncrement; - float saturationGained = newSaturationValue > newFoodValue ? newFoodValue - stats.getSaturationLevel() : foodSaturationIncrement; - // Redraw saturation overlay for gained - drawSaturationOverlay(saturationRenderEvent, mc, saturationGained, flashAlpha); - } - } - } - - public static void drawSaturationOverlay(float saturationGained, float saturationLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) { - float totalSaturation = saturationLevel + saturationGained; - if (totalSaturation <= 0) return; - - int iconSize = 9; - int maxBars = 10; - float saturationPerLayer = 20f; - float saturationPerBar = saturationPerLayer / maxBars; - - int[] colors = ModConfig.SATURATION_HUD_OVERLAY_COLORS.get() - .stream() - .mapToInt(color -> { - try { - return Integer.decode(color); - } catch (NumberFormatException e) { - AppleSkin.Log.warn("Invalid color value in config: {}", color); - return 0xFFD500; // fallback (yellow) - } - }) - .toArray(); - - - int fullLayers = (int) (totalSaturation / saturationPerLayer); - float remainder = totalSaturation % saturationPerLayer; - - // Draw all full layers - for (int layer = 0; layer < fullLayers; layer++) { - int color = (int) alpha | colors[layer % colors.length]; - for (int i = 0; i < maxBars; i++) { - IntPoint offset = foodBarOffsets.get(i); - if (offset == null) continue; - - int x = right + offset.x; - int y = top + offset.y; - drawIconTinted(guiGraphics, x, y, 7 * iconSize, iconSize, iconSize, color); - } - } - - // Draw partial layer if there is remaining saturation - if (remainder > 0) { - int color = (int) alpha | colors[fullLayers % colors.length]; - int fullBars = (int) (remainder / saturationPerBar); - float lastBarFraction = remainder % saturationPerBar; - - for (int i = 0; i < fullBars; i++) { - IntPoint offset = foodBarOffsets.get(i); - if (offset == null) continue; - int x = right + offset.x; - int y = top + offset.y; - drawIconTinted(guiGraphics, x, y, 7 * iconSize, iconSize, iconSize, color); - } - - if (lastBarFraction > 0) { - IntPoint offset = foodBarOffsets.get(fullBars); - if (offset != null) { - int x = right + offset.x; - int y = top + offset.y; - int u = lastBarFraction >= 1.5f ? 6 * iconSize - : lastBarFraction >= 1f ? 5 * iconSize - : lastBarFraction > .5f ? 4 * iconSize - : 0; - drawIconTinted(guiGraphics, x, y, u, iconSize, iconSize, color); - } - } - } - } - - private static void drawIconTinted(GuiGraphics guiGraphics, int x, int y, int u, int width, int height, int color) { - float r = FastColor.ARGB32.red(color) / 255f; - float g = FastColor.ARGB32.green(color) / 255f; - float b = FastColor.ARGB32.blue(color) / 255f; - float a = FastColor.ARGB32.alpha(color) / 255f; - - guiGraphics.setColor(r, g, b, a); - guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, 0, width, height); - } - - public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha, boolean useRottenTextures) { - if (hungerRestored <= 0) - return; - - enableAlpha(alpha); - - int modifiedFood = Math.max(0, Math.min(20, foodLevel + hungerRestored)); - - int startFoodBars = Math.max(0, foodLevel / 2); - int endFoodBars = (int) Math.ceil(modifiedFood / 2.0F); - - int iconStartOffset = 16; - int iconSize = 9; - - for (int i = startFoodBars; i < endFoodBars; ++i) { - // gets the offset that needs to be render of icon - IntPoint offset = foodBarOffsets.get(i); - if (offset == null) - continue; - - int x = right + offset.x; - int y = top + offset.y; - - // location to normal food by default - int v = 3 * iconSize; - int u = iconStartOffset + 4 * iconSize; - int ub = iconStartOffset + 1 * iconSize; - - // relocation to rotten food - if (useRottenTextures) { - u += 4 * iconSize; - ub += 12 * iconSize; - } - - // relocation to half food - if (i * 2 + 1 == modifiedFood) - u += 1 * iconSize; - - // very faint background - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha * 0.25F); - guiGraphics.blit(TextureHelper.MC_ICONS, x, y, ub, v, iconSize, iconSize); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); - - guiGraphics.blit(TextureHelper.MC_ICONS, x, y, u, v, iconSize, iconSize); - } - - disableAlpha(alpha); - } - - public static void drawHealthOverlay(float health, float modifiedHealth, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) { - if (modifiedHealth <= health) - return; - - enableAlpha(alpha); - - int fixedModifiedHealth = (int) Math.ceil(modifiedHealth); - boolean isHardcore = mc.player.level() != null && mc.player.level().getLevelData().isHardcore(); - - int startHealthBars = (int) Math.max(0, (Math.ceil(health) / 2.0F)); - int endHealthBars = (int) Math.max(0, Math.ceil(modifiedHealth / 2.0F)); - - int iconStartOffset = 16; - int iconSize = 9; - - for (int i = startHealthBars; i < endHealthBars; ++i) { - // gets the offset that needs to be render of icon - IntPoint offset = healthBarOffsets.get(i); - if (offset == null) - continue; - - int x = right + offset.x; - int y = top + offset.y; - - // location to full heart icon by default - int v = 0 * iconSize; - int u = iconStartOffset + 4 * iconSize; - int ub = iconStartOffset + 1 * iconSize; - - // relocation to half heart - if (i * 2 + 1 == fixedModifiedHealth) - u += 1 * iconSize; - - // relocation to special heart of hardcore - if (isHardcore) - v = 5 * iconSize; - - //// apply the status effects of the player - //if (player.hasStatusEffect(StatusEffects.POISON)) { - // u += 4 * iconSize; - //} else if (player.hasStatusEffect(StatusEffects.WITHER)) { - // u += 8 * iconSize; - //} - - // very faint background - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha * 0.25F); - guiGraphics.blit(TextureHelper.MC_ICONS, x, y, ub, v, iconSize, iconSize); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); - - guiGraphics.blit(TextureHelper.MC_ICONS, x, y, u, v, iconSize, iconSize); - } - - disableAlpha(alpha); - } - - public static void drawExhaustionOverlay(float exhaustion, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) { - float maxExhaustion = HungerHelper.getMaxExhaustion(mc.player); - // clamp between 0 and 1 - float ratio = Math.min(1, Math.max(0, exhaustion / maxExhaustion)); - int width = (int) (ratio * 81); - int height = 9; - - enableAlpha(.75f); - guiGraphics.blit(TextureHelper.MOD_ICONS, right - width, top, 81 - width, 18, width, height); - disableAlpha(.75f); - - // rebind default icons - RenderSystem.setShaderTexture(0, TextureHelper.MC_ICONS); - } - - - public static void enableAlpha(float alpha) { - RenderSystem.enableBlend(); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); - RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - } - - public static void disableAlpha(float alpha) { - RenderSystem.disableBlend(); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - } - - @SubscribeEvent - public void onClientTick(TickEvent.ClientTickEvent event) { - if (event.phase != TickEvent.Phase.END) - return; - - unclampedFlashAlpha += alphaDir * 0.125f; - if (unclampedFlashAlpha >= 1.5f) { - alphaDir = -1; - } else if (unclampedFlashAlpha <= -0.5f) { - alphaDir = 1; - } - flashAlpha = Math.max(0F, Math.min(1F, unclampedFlashAlpha)) * Math.max(0F, Math.min(1F, ModConfig.MAX_HUD_OVERLAY_FLASH_ALPHA.get().floatValue())); - } - - public static void resetFlash() { - unclampedFlashAlpha = flashAlpha = 0f; - alphaDir = 1; - } - - private static void drawSaturationOverlay(HUDOverlayEvent.Saturation event, Minecraft mc, float saturationGained, float alpha) { - drawSaturationOverlay(saturationGained, event.saturationLevel, mc, event.guiGraphics, event.x, event.y, alpha); - } - - private static void drawHungerOverlay(HUDOverlayEvent.HungerRestored event, Minecraft mc, int hunger, float alpha, boolean useRottenTextures) { - drawHungerOverlay(hunger, event.currentFoodLevel, mc, event.guiGraphics, event.x, event.y, alpha, useRottenTextures); - } - - private static void drawHealthOverlay(HUDOverlayEvent.HealthRestored event, Minecraft mc, float alpha) { - drawHealthOverlay(mc.player.getHealth(), event.modifiedHealth, mc, event.guiGraphics, event.x, event.y, alpha); - } - - private static void drawExhaustionOverlay(HUDOverlayEvent.Exhaustion event, Minecraft mc, float alpha) { - drawExhaustionOverlay(event.exhaustion, mc, event.guiGraphics, event.x, event.y, alpha); - } - - private static boolean shouldRenderAnyOverlays() { - return ModConfig.SHOW_FOOD_VALUES_OVERLAY.get() || ModConfig.SHOW_SATURATION_OVERLAY.get() || ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get(); - } - - private static boolean shouldShowEstimatedHealth(ItemStack hoveredStack, FoodValues modifiedFoodValues) { - // then configuration cancel the render event - if (!ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get()) - return false; - - Minecraft mc = Minecraft.getInstance(); - Player player = mc.player; - FoodData stats = player.getFoodData(); - - // in the `PEACEFUL` mode, health will restore faster - if (player.level().getDifficulty() == Difficulty.PEACEFUL) - return false; - - // when player has any changes health amount by any case can't show estimated health - // because player will confused how much of restored/damaged healths - if (stats.getFoodLevel() >= 18) - return false; - - if (player.hasEffect(MobEffects.POISON)) - return false; - - if (player.hasEffect(MobEffects.WITHER)) - return false; - - if (player.hasEffect(MobEffects.REGENERATION)) - return false; - - return true; - } - - private static void generateHealthBarOffsets(int top, int left, int right, int ticks, Player player) { - // hard code in `InGameHUD` - random.setSeed((long) (ticks * 312871L)); - - final int preferHealthBars = 10; - final float maxHealth = player.getMaxHealth(); - final float absorptionHealth = (float) Math.ceil(player.getAbsorptionAmount()); - - int healthBars = (int) Math.ceil((maxHealth + absorptionHealth) / 2.0F); - // When maxHealth + absorptionHealth is greater than Integer.INT_MAX, - // Minecraft will disable heart rendering due to a quirk of MathHelper.ceil. - // We have a much lower threshold since there's no reason to get the offsets - // for thousands of hearts. - // Note: Infinite and > INT_MAX absorption has been seen in the wild. - // This will effectively disable rendering whenever health is unexpectedly large. - if (healthBars < 0 || healthBars > 1000) { - healthBarOffsets.setSize(0); - return; - } - - int healthRows = (int) Math.ceil((float) healthBars / 10.0F); - - int healthRowHeight = Math.max(10 - (healthRows - 2), 3); - - boolean shouldAnimatedHealth = false; - - // when some mods using custom render, we need to least provide an option to cancel animation - if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) { - // in vanilla health is too low (below 5) will show heartbeat animation - // when regeneration will also show heartbeat animation, but we don't need now - shouldAnimatedHealth = Math.ceil(player.getHealth()) <= 4; - } - - // adjust the size - if (healthBarOffsets.size() != healthBars) - healthBarOffsets.setSize(healthBars); - - // left alignment, multiple rows, reverse - for (int i = healthBars - 1; i >= 0; --i) { - int row = (int) Math.ceil((float) (i + 1) / (float) preferHealthBars) - 1; - int x = left + i % preferHealthBars * 8; - int y = top - row * healthRowHeight; - // apply the animated offset - if (shouldAnimatedHealth) - y += random.nextInt(2); - - // reuse the point object to reduce memory usage - IntPoint point = healthBarOffsets.get(i); - if (point == null) { - point = new IntPoint(); - healthBarOffsets.set(i, point); - } - - point.x = x - left; - point.y = y - top; - } - } - - private static void generateHungerBarOffsets(int top, int left, int right, int ticks, Player player) { - final int preferFoodBars = 10; - - boolean shouldAnimatedFood = false; - - // when some mods using custom render, we need to least provide an option to cancel animation - if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) { - FoodData stats = player.getFoodData(); - - // in vanilla saturation level is zero will show hunger animation - float saturationLevel = stats.getSaturationLevel(); - int foodLevel = stats.getFoodLevel(); - shouldAnimatedFood = saturationLevel <= 0.0F && ticks % (foodLevel * 3 + 1) == 0; - } - - if (foodBarOffsets.size() != preferFoodBars) - foodBarOffsets.setSize(preferFoodBars); - - // right alignment, single row - for (int i = 0; i < preferFoodBars; ++i) { - int x = right - i * 8 - 9; - int y = top; - - // apply the animated offset - if (shouldAnimatedFood) - y += random.nextInt(3) - 1; - - // reuse the point object to reduce memory usage - IntPoint point = foodBarOffsets.get(i); - if (point == null) { - point = new IntPoint(); - foodBarOffsets.set(i, point); - } - - point.x = x - right; - point.y = y - top; - } - } +public class HUDOverlayHandler +{ + private static float unclampedFlashAlpha = 0f; + private static float flashAlpha = 0f; + private static byte alphaDir = 1; + protected static int foodIconsOffset; + + public static final Vector healthBarOffsets = new Vector<>(); + public static final Vector foodBarOffsets = new Vector<>(); + + private static final Random random = new Random(); + + public static void init() + { + MinecraftForge.EVENT_BUS.register(new HUDOverlayHandler()); + } + + static ResourceLocation FOOD_LEVEL_ELEMENT = new ResourceLocation("minecraft", "food_level"); + static ResourceLocation PLAYER_HEALTH_ELEMENT = new ResourceLocation("minecraft", "player_health"); + + @SubscribeEvent + public void onRenderGuiOverlayPre(RenderGuiOverlayEvent.Pre event) + { + if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) + { + Minecraft mc = Minecraft.getInstance(); + ForgeGui gui = (ForgeGui) mc.gui; + boolean isMounted = mc.player.getVehicle() instanceof LivingEntity; + if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) + { + renderExhaustion(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight()); + } + } + } + + @SubscribeEvent + public void onRenderGuiOverlayPost(RenderGuiOverlayEvent.Post event) + { + if (event.getOverlay() == GuiOverlayManager.findOverlay(FOOD_LEVEL_ELEMENT)) + { + Minecraft mc = Minecraft.getInstance(); + ForgeGui gui = (ForgeGui) mc.gui; + boolean isMounted = mc.player.getVehicle() instanceof LivingEntity; + if (!isMounted && !mc.options.hideGui && gui.shouldDrawSurvivalElements()) + { + renderFoodOrHealthOverlay(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight(), RenderOverlayType.FOOD); + } + } + else if (event.getOverlay() == GuiOverlayManager.findOverlay(PLAYER_HEALTH_ELEMENT)) + { + Minecraft mc = Minecraft.getInstance(); + ForgeGui gui = (ForgeGui) mc.gui; + if (!mc.options.hideGui && gui.shouldDrawSurvivalElements()) + { + renderFoodOrHealthOverlay(gui, event.getGuiGraphics(), event.getPartialTick(), event.getWindow().getScreenWidth(), event.getWindow().getScreenHeight(), RenderOverlayType.HEALTH); + } + } + } + + public static void renderExhaustion(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight) + { + foodIconsOffset = gui.rightHeight; + + if (!ModConfig.SHOW_FOOD_EXHAUSTION_UNDERLAY.get()) return; + + Minecraft mc = Minecraft.getInstance(); + Player player = mc.player; + assert player != null; + + int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; + int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; + float exhaustion = player.getFoodData().getExhaustionLevel(); + + // Notify everyone that we should render exhaustion hud overlay + HUDOverlayEvent.Exhaustion renderEvent = new HUDOverlayEvent.Exhaustion(exhaustion, right, top, guiGraphics); + MinecraftForge.EVENT_BUS.post(renderEvent); + if (!renderEvent.isCanceled()) drawExhaustionOverlay(renderEvent, mc, 1f); + } + + enum RenderOverlayType + { + HEALTH, FOOD, + } + + public static void renderFoodOrHealthOverlay(ForgeGui gui, GuiGraphics guiGraphics, float partialTicks, int screenWidth, int screenHeight, RenderOverlayType type) + { + if (!shouldRenderAnyOverlays()) return; + + Minecraft mc = Minecraft.getInstance(); + Player player = mc.player; + assert player != null; + FoodData stats = player.getFoodData(); + + int top = mc.getWindow().getGuiScaledHeight() - foodIconsOffset; + int left = mc.getWindow().getGuiScaledWidth() / 2 - 91; // left of health bar + int right = mc.getWindow().getGuiScaledWidth() / 2 + 91; // right of food bar + + if (type == RenderOverlayType.HEALTH) generateHealthBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); + if (type == RenderOverlayType.FOOD) generateHungerBarOffsets(top, left, right, mc.gui.getGuiTicks(), player); + + HUDOverlayEvent.Saturation saturationRenderEvent = null; + if (type == RenderOverlayType.FOOD) + { + saturationRenderEvent = new HUDOverlayEvent.Saturation(stats.getSaturationLevel(), right, top, guiGraphics); + + // cancel render overlay event when configuration disabled. + if (!ModConfig.SHOW_SATURATION_OVERLAY.get()) saturationRenderEvent.setCanceled(true); + + // notify everyone that we should render saturation hud overlay + if (!saturationRenderEvent.isCanceled()) MinecraftForge.EVENT_BUS.post(saturationRenderEvent); + + // the render saturation event maybe cancelled by other mods + if (!saturationRenderEvent.isCanceled()) drawSaturationOverlay(saturationRenderEvent, mc, 0, 1f); + } + + // try to get the item stack in the player hand + ItemStack heldItem = player.getMainHandItem(); + if (ModConfig.SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND.get() && !FoodHelper.canConsume(heldItem, player)) + heldItem = player.getOffhandItem(); + + boolean shouldRenderHeldItemValues = !heldItem.isEmpty() && FoodHelper.canConsume(heldItem, player); + if (!shouldRenderHeldItemValues) + { + resetFlash(); + return; + } + + FoodValues modifiedFoodValues = FoodHelper.getModifiedFoodValues(heldItem, player); + FoodValuesEvent foodValuesEvent = new FoodValuesEvent(player, heldItem, FoodHelper.getDefaultFoodValues(heldItem, player), modifiedFoodValues); + MinecraftForge.EVENT_BUS.post(foodValuesEvent); + modifiedFoodValues = foodValuesEvent.modifiedFoodValues; + + if (type == RenderOverlayType.HEALTH) + { + // Offsets size is set to zero intentionally to disable rendering when health is infinite. + if (healthBarOffsets.isEmpty()) return; + + if (!shouldShowEstimatedHealth(heldItem, modifiedFoodValues)) return; + + float foodHealthIncrement = FoodHelper.getEstimatedHealthIncrement(heldItem, modifiedFoodValues, player); + float currentHealth = player.getHealth(); + float modifiedHealth = Math.min(currentHealth + foodHealthIncrement, player.getMaxHealth()); + + // only create object when the estimated health is successfully + HUDOverlayEvent.HealthRestored healthRenderEvent = null; + if (currentHealth < modifiedHealth) + healthRenderEvent = new HUDOverlayEvent.HealthRestored(modifiedHealth, heldItem, modifiedFoodValues, left, top, guiGraphics); + + // notify everyone that we should render estimated health hud + if (healthRenderEvent != null) MinecraftForge.EVENT_BUS.post(healthRenderEvent); + + if (healthRenderEvent != null && !healthRenderEvent.isCanceled()) + drawHealthOverlay(healthRenderEvent, mc, flashAlpha); + } + else if (type == RenderOverlayType.FOOD) + { + if (!ModConfig.SHOW_FOOD_VALUES_OVERLAY.get()) return; + + // notify everyone that we should render hunger hud overlay + HUDOverlayEvent.HungerRestored renderRenderEvent = new HUDOverlayEvent.HungerRestored(stats.getFoodLevel(), heldItem, modifiedFoodValues, right, top, guiGraphics); + MinecraftForge.EVENT_BUS.post(renderRenderEvent); + if (renderRenderEvent.isCanceled()) return; + + // calculate the final hunger and saturation + int foodHunger = modifiedFoodValues.hunger; + float foodSaturationIncrement = modifiedFoodValues.getSaturationIncrement(); + + // restored hunger/saturation overlay while holding food + drawHungerOverlay(renderRenderEvent, mc, foodHunger, flashAlpha, FoodHelper.isRotten(heldItem, player)); + + // The render saturation overlay event maybe cancelled by other mods + if (!saturationRenderEvent.isCanceled()) + { + int newFoodValue = stats.getFoodLevel() + foodHunger; + float newSaturationValue = stats.getSaturationLevel() + foodSaturationIncrement; + float saturationGained = newSaturationValue > newFoodValue ? newFoodValue - stats.getSaturationLevel() : foodSaturationIncrement; + // Redraw saturation overlay for gained + drawSaturationOverlay(saturationRenderEvent, mc, saturationGained, flashAlpha); + } + } + } + + public static void drawSaturationOverlay(float saturationGained, float saturationLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) + { + float totalSaturation = saturationLevel + saturationGained; + if (totalSaturation <= 0) return; + + int iconSize = 9; + int maxBars = 10; + float saturationPerLayer = 20f; + float saturationPerBar = saturationPerLayer / maxBars; + + int[] colors = ModConfig.SATURATION_HUD_OVERLAY_COLORS.get().stream().mapToInt(color -> { + try + { + return Integer.decode(color); + } + catch (NumberFormatException e) + { + AppleSkin.Log.warn("Invalid color value in config: {}", color); + return 0xFFD500; // fallback (yellow) + } + }).toArray(); + + + int fullLayers = (int) (totalSaturation / saturationPerLayer); + float remainder = totalSaturation % saturationPerLayer; + + // Draw all full layers + for (int layer = 0; layer < fullLayers; layer++) + { + int color = (int) alpha | colors[layer % colors.length]; + for (int i = 0; i < maxBars; i++) + { + IntPoint offset = foodBarOffsets.get(i); + if (offset == null) continue; + + int x = right + offset.x; + int y = top + offset.y; + drawIconTinted(guiGraphics, x, y, 7 * iconSize, iconSize, iconSize, color); + } + } + + // Draw partial layer if there is remaining saturation + if (remainder > 0) + { + int color = (int) alpha | colors[fullLayers % colors.length]; + int fullBars = (int) (remainder / saturationPerBar); + float lastBarFraction = remainder % saturationPerBar; + + for (int i = 0; i < fullBars; i++) + { + IntPoint offset = foodBarOffsets.get(i); + if (offset == null) continue; + int x = right + offset.x; + int y = top + offset.y; + drawIconTinted(guiGraphics, x, y, 7 * iconSize, iconSize, iconSize, color); + } + + if (lastBarFraction > 0) + { + IntPoint offset = foodBarOffsets.get(fullBars); + if (offset != null) + { + int x = right + offset.x; + int y = top + offset.y; + int u = lastBarFraction >= 1.5f ? 6 * iconSize : lastBarFraction >= 1f ? 5 * iconSize : lastBarFraction > .5f ? 4 * iconSize : 0; + drawIconTinted(guiGraphics, x, y, u, iconSize, iconSize, color); + } + } + } + } + + private static void drawIconTinted(GuiGraphics guiGraphics, int x, int y, int u, int width, int height, int color) + { + float r = FastColor.ARGB32.red(color) / 255f; + float g = FastColor.ARGB32.green(color) / 255f; + float b = FastColor.ARGB32.blue(color) / 255f; + float a = FastColor.ARGB32.alpha(color) / 255f; + + guiGraphics.setColor(r, g, b, a); + guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, 0, width, height); + } + + public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha, boolean useRottenTextures) + { + if (hungerRestored <= 0) return; + + enableAlpha(alpha); + + int modifiedFood = Math.max(0, Math.min(20, foodLevel + hungerRestored)); + + int startFoodBars = Math.max(0, foodLevel / 2); + int endFoodBars = (int) Math.ceil(modifiedFood / 2.0F); + + int iconStartOffset = 16; + int iconSize = 9; + + for (int i = startFoodBars; i < endFoodBars; ++i) + { + // gets the offset that needs to be render of icon + IntPoint offset = foodBarOffsets.get(i); + if (offset == null) continue; + + int x = right + offset.x; + int y = top + offset.y; + + // location to normal food by default + int v = 3 * iconSize; + int u = iconStartOffset + 4 * iconSize; + int ub = iconStartOffset + 1 * iconSize; + + // relocation to rotten food + if (useRottenTextures) + { + u += 4 * iconSize; + ub += 12 * iconSize; + } + + // relocation to half food + if (i * 2 + 1 == modifiedFood) u += 1 * iconSize; + + // very faint background + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha * 0.25F); + guiGraphics.blit(TextureHelper.MC_ICONS, x, y, ub, v, iconSize, iconSize); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); + + guiGraphics.blit(TextureHelper.MC_ICONS, x, y, u, v, iconSize, iconSize); + } + + disableAlpha(alpha); + } + + public static void drawHealthOverlay(float health, float modifiedHealth, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) + { + if (modifiedHealth <= health) return; + + enableAlpha(alpha); + + int fixedModifiedHealth = (int) Math.ceil(modifiedHealth); + boolean isHardcore = mc.player.level() != null && mc.player.level().getLevelData().isHardcore(); + + int startHealthBars = (int) Math.max(0, (Math.ceil(health) / 2.0F)); + int endHealthBars = (int) Math.max(0, Math.ceil(modifiedHealth / 2.0F)); + + int iconStartOffset = 16; + int iconSize = 9; + + for (int i = startHealthBars; i < endHealthBars; ++i) + { + // gets the offset that needs to be render of icon + IntPoint offset = healthBarOffsets.get(i); + if (offset == null) continue; + + int x = right + offset.x; + int y = top + offset.y; + + // location to full heart icon by default + int v = 0 * iconSize; + int u = iconStartOffset + 4 * iconSize; + int ub = iconStartOffset + 1 * iconSize; + + // relocation to half heart + if (i * 2 + 1 == fixedModifiedHealth) u += 1 * iconSize; + + // relocation to special heart of hardcore + if (isHardcore) v = 5 * iconSize; + + //// apply the status effects of the player + //if (player.hasStatusEffect(StatusEffects.POISON)) { + // u += 4 * iconSize; + //} else if (player.hasStatusEffect(StatusEffects.WITHER)) { + // u += 8 * iconSize; + //} + + // very faint background + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha * 0.25F); + guiGraphics.blit(TextureHelper.MC_ICONS, x, y, ub, v, iconSize, iconSize); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); + + guiGraphics.blit(TextureHelper.MC_ICONS, x, y, u, v, iconSize, iconSize); + } + + disableAlpha(alpha); + } + + public static void drawExhaustionOverlay(float exhaustion, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha) + { + float maxExhaustion = HungerHelper.getMaxExhaustion(mc.player); + // clamp between 0 and 1 + float ratio = Math.min(1, Math.max(0, exhaustion / maxExhaustion)); + int width = (int) (ratio * 81); + int height = 9; + + enableAlpha(.75f); + guiGraphics.blit(TextureHelper.MOD_ICONS, right - width, top, 81 - width, 18, width, height); + disableAlpha(.75f); + + // rebind default icons + RenderSystem.setShaderTexture(0, TextureHelper.MC_ICONS); + } + + + public static void enableAlpha(float alpha) + { + RenderSystem.enableBlend(); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, alpha); + RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + } + + public static void disableAlpha(float alpha) + { + RenderSystem.disableBlend(); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + } + + @SubscribeEvent + public void onClientTick(TickEvent.ClientTickEvent event) + { + if (event.phase != TickEvent.Phase.END) return; + + unclampedFlashAlpha += alphaDir * 0.125f; + if (unclampedFlashAlpha >= 1.5f) + { + alphaDir = -1; + } + else if (unclampedFlashAlpha <= -0.5f) + { + alphaDir = 1; + } + flashAlpha = Math.max(0F, Math.min(1F, unclampedFlashAlpha)) * Math.max(0F, Math.min(1F, ModConfig.MAX_HUD_OVERLAY_FLASH_ALPHA.get().floatValue())); + } + + public static void resetFlash() + { + unclampedFlashAlpha = flashAlpha = 0f; + alphaDir = 1; + } + + private static void drawSaturationOverlay(HUDOverlayEvent.Saturation event, Minecraft mc, float saturationGained, float alpha) + { + drawSaturationOverlay(saturationGained, event.saturationLevel, mc, event.guiGraphics, event.x, event.y, alpha); + } + + private static void drawHungerOverlay(HUDOverlayEvent.HungerRestored event, Minecraft mc, int hunger, float alpha, boolean useRottenTextures) + { + drawHungerOverlay(hunger, event.currentFoodLevel, mc, event.guiGraphics, event.x, event.y, alpha, useRottenTextures); + } + + private static void drawHealthOverlay(HUDOverlayEvent.HealthRestored event, Minecraft mc, float alpha) + { + drawHealthOverlay(mc.player.getHealth(), event.modifiedHealth, mc, event.guiGraphics, event.x, event.y, alpha); + } + + private static void drawExhaustionOverlay(HUDOverlayEvent.Exhaustion event, Minecraft mc, float alpha) + { + drawExhaustionOverlay(event.exhaustion, mc, event.guiGraphics, event.x, event.y, alpha); + } + + private static boolean shouldRenderAnyOverlays() + { + return ModConfig.SHOW_FOOD_VALUES_OVERLAY.get() || ModConfig.SHOW_SATURATION_OVERLAY.get() || ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get(); + } + + private static boolean shouldShowEstimatedHealth(ItemStack hoveredStack, FoodValues modifiedFoodValues) + { + // then configuration cancel the render event + if (!ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get()) return false; + + Minecraft mc = Minecraft.getInstance(); + Player player = mc.player; + FoodData stats = player.getFoodData(); + + // in the `PEACEFUL` mode, health will restore faster + if (player.level().getDifficulty() == Difficulty.PEACEFUL) return false; + + // when player has any changes health amount by any case can't show estimated health + // because player will confused how much of restored/damaged healths + if (stats.getFoodLevel() >= 18) return false; + + if (player.hasEffect(MobEffects.POISON)) return false; + + if (player.hasEffect(MobEffects.WITHER)) return false; + + if (player.hasEffect(MobEffects.REGENERATION)) return false; + + return true; + } + + private static void generateHealthBarOffsets(int top, int left, int right, int ticks, Player player) + { + // hard code in `InGameHUD` + random.setSeed((long) (ticks * 312871L)); + + final int preferHealthBars = 10; + final float maxHealth = player.getMaxHealth(); + final float absorptionHealth = (float) Math.ceil(player.getAbsorptionAmount()); + + int healthBars = (int) Math.ceil((maxHealth + absorptionHealth) / 2.0F); + // When maxHealth + absorptionHealth is greater than Integer.INT_MAX, + // Minecraft will disable heart rendering due to a quirk of MathHelper.ceil. + // We have a much lower threshold since there's no reason to get the offsets + // for thousands of hearts. + // Note: Infinite and > INT_MAX absorption has been seen in the wild. + // This will effectively disable rendering whenever health is unexpectedly large. + if (healthBars < 0 || healthBars > 1000) + { + healthBarOffsets.setSize(0); + return; + } + + int healthRows = (int) Math.ceil((float) healthBars / 10.0F); + + int healthRowHeight = Math.max(10 - (healthRows - 2), 3); + + boolean shouldAnimatedHealth = false; + + // when some mods using custom render, we need to least provide an option to cancel animation + if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) + { + // in vanilla health is too low (below 5) will show heartbeat animation + // when regeneration will also show heartbeat animation, but we don't need now + shouldAnimatedHealth = Math.ceil(player.getHealth()) <= 4; + } + + // adjust the size + if (healthBarOffsets.size() != healthBars) healthBarOffsets.setSize(healthBars); + + // left alignment, multiple rows, reverse + for (int i = healthBars - 1; i >= 0; --i) + { + int row = (int) Math.ceil((float) (i + 1) / (float) preferHealthBars) - 1; + int x = left + i % preferHealthBars * 8; + int y = top - row * healthRowHeight; + // apply the animated offset + if (shouldAnimatedHealth) y += random.nextInt(2); + + // reuse the point object to reduce memory usage + IntPoint point = healthBarOffsets.get(i); + if (point == null) + { + point = new IntPoint(); + healthBarOffsets.set(i, point); + } + + point.x = x - left; + point.y = y - top; + } + } + + private static void generateHungerBarOffsets(int top, int left, int right, int ticks, Player player) + { + final int preferFoodBars = 10; + + boolean shouldAnimatedFood = false; + + // when some mods using custom render, we need to least provide an option to cancel animation + if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) + { + FoodData stats = player.getFoodData(); + + // in vanilla saturation level is zero will show hunger animation + float saturationLevel = stats.getSaturationLevel(); + int foodLevel = stats.getFoodLevel(); + shouldAnimatedFood = saturationLevel <= 0.0F && ticks % (foodLevel * 3 + 1) == 0; + } + + if (foodBarOffsets.size() != preferFoodBars) foodBarOffsets.setSize(preferFoodBars); + + // right alignment, single row + for (int i = 0; i < preferFoodBars; ++i) + { + int x = right - i * 8 - 9; + int y = top; + + // apply the animated offset + if (shouldAnimatedFood) y += random.nextInt(3) - 1; + + // reuse the point object to reduce memory usage + IntPoint point = foodBarOffsets.get(i); + if (point == null) + { + point = new IntPoint(); + foodBarOffsets.set(i, point); + } + + point.x = x - right; + point.y = y - top; + } + } } From 778a378a597b0174f575bbfca3fd29b3c408a541 Mon Sep 17 00:00:00 2001 From: Mysticpasta1 Date: Fri, 6 Feb 2026 17:24:51 -0600 Subject: [PATCH 08/11] try to fix lag from the HUD sat colors --- gradle.properties | 2 +- .../appleskin/client/HUDOverlayHandler.java | 62 +++++++++++-------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/gradle.properties b/gradle.properties index a233583..2f2de43 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,4 +11,4 @@ org.gradle.daemon=false # Mod Properties maven_group = squeek.appleskin archives_base_name = appleskin-forge -mod_version = 2.6.0 +mod_version = 2.6.1 diff --git a/java/squeek/appleskin/client/HUDOverlayHandler.java b/java/squeek/appleskin/client/HUDOverlayHandler.java index 71ac3b5..8be6d8e 100644 --- a/java/squeek/appleskin/client/HUDOverlayHandler.java +++ b/java/squeek/appleskin/client/HUDOverlayHandler.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Random; import java.util.Vector; +import java.util.stream.Collectors; @OnlyIn(Dist.CLIENT) public class HUDOverlayHandler @@ -45,6 +46,9 @@ public class HUDOverlayHandler private static byte alphaDir = 1; protected static int foodIconsOffset; + private static List lastSaturationHudOverlayColorsConfig; + private static int[] cachedSaturationHudOverlayColors; + public static final Vector healthBarOffsets = new Vector<>(); public static final Vector foodBarOffsets = new Vector<>(); @@ -230,18 +234,23 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio float saturationPerLayer = 20f; float saturationPerBar = saturationPerLayer / maxBars; - int[] colors = ModConfig.SATURATION_HUD_OVERLAY_COLORS.get().stream().mapToInt(color -> { - try - { - return Integer.decode(color); - } - catch (NumberFormatException e) - { - AppleSkin.Log.warn("Invalid color value in config: {}", color); - return 0xFFD500; // fallback (yellow) - } - }).toArray(); - + List currentColorsConfig = ModConfig.SATURATION_HUD_OVERLAY_COLORS.get(); + if (cachedSaturationHudOverlayColors == null || !currentColorsConfig.equals(lastSaturationHudOverlayColorsConfig)) + { + cachedSaturationHudOverlayColors = currentColorsConfig.stream().mapToInt(color -> { + try + { + return Integer.decode(color); + } + catch (NumberFormatException e) + { + AppleSkin.Log.warn("Invalid color value in config: {}", color); + return 0xFFD500; // fallback (yellow) + } + }).toArray(); + lastSaturationHudOverlayColorsConfig = new ArrayList<>(currentColorsConfig); + } + int[] colors = cachedSaturationHudOverlayColors; int fullLayers = (int) (totalSaturation / saturationPerLayer); float remainder = totalSaturation % saturationPerLayer; @@ -250,6 +259,12 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio for (int layer = 0; layer < fullLayers; layer++) { int color = (int) alpha | colors[layer % colors.length]; + float r = ((color >> 16) & 255) / 255f; + float g = ((color >> 8) & 255) / 255f; + float b = (color & 255) / 255f; + float a = ((color >> 24) & 255) / 255f; + guiGraphics.setColor(r, g, b, a); + for (int i = 0; i < maxBars; i++) { IntPoint offset = foodBarOffsets.get(i); @@ -257,7 +272,7 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio int x = right + offset.x; int y = top + offset.y; - drawIconTinted(guiGraphics, x, y, 7 * iconSize, iconSize, iconSize, color); + guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, 7 * iconSize, 0, iconSize, iconSize); } } @@ -265,6 +280,12 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio if (remainder > 0) { int color = (int) alpha | colors[fullLayers % colors.length]; + float r = ((color >> 16) & 255) / 255f; + float g = ((color >> 8) & 255) / 255f; + float b = (color & 255) / 255f; + float a = ((color >> 24) & 255) / 255f; + guiGraphics.setColor(r, g, b, a); + int fullBars = (int) (remainder / saturationPerBar); float lastBarFraction = remainder % saturationPerBar; @@ -274,7 +295,7 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio if (offset == null) continue; int x = right + offset.x; int y = top + offset.y; - drawIconTinted(guiGraphics, x, y, 7 * iconSize, iconSize, iconSize, color); + guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, 7 * iconSize, 0, iconSize, iconSize); } if (lastBarFraction > 0) @@ -285,21 +306,12 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio int x = right + offset.x; int y = top + offset.y; int u = lastBarFraction >= 1.5f ? 6 * iconSize : lastBarFraction >= 1f ? 5 * iconSize : lastBarFraction > .5f ? 4 * iconSize : 0; - drawIconTinted(guiGraphics, x, y, u, iconSize, iconSize, color); + guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, 0, iconSize, iconSize); } } } - } - - private static void drawIconTinted(GuiGraphics guiGraphics, int x, int y, int u, int width, int height, int color) - { - float r = FastColor.ARGB32.red(color) / 255f; - float g = FastColor.ARGB32.green(color) / 255f; - float b = FastColor.ARGB32.blue(color) / 255f; - float a = FastColor.ARGB32.alpha(color) / 255f; - guiGraphics.setColor(r, g, b, a); - guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, 0, width, height); + guiGraphics.setColor(1.0F, 1.0F, 1.0F, 1.0F); } public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha, boolean useRottenTextures) From fd505d55a6cdda2cdcd1b5acceee2de8fbac7a91 Mon Sep 17 00:00:00 2001 From: Mysticpasta1 Date: Sun, 8 Feb 2026 01:14:35 -0600 Subject: [PATCH 09/11] put caching in the mod config area under events --- java/squeek/appleskin/AppleSkin.java | 2 + java/squeek/appleskin/ModConfig.java | 48 +++++++++++++++++++ .../appleskin/client/HUDOverlayHandler.java | 28 +---------- 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/java/squeek/appleskin/AppleSkin.java b/java/squeek/appleskin/AppleSkin.java index ef4a4f2..143d9db 100644 --- a/java/squeek/appleskin/AppleSkin.java +++ b/java/squeek/appleskin/AppleSkin.java @@ -33,6 +33,8 @@ public AppleSkin() net.minecraftforge.fml.config.ModConfig.Type.CLIENT, ModConfig.SPEC ); + FMLJavaModLoadingContext.get().getModEventBus().addListener(ModConfig::onConfigReloading); + FMLJavaModLoadingContext.get().getModEventBus().addListener(ModConfig::onConfigLoading); ModConfig.init(FMLPaths.CONFIGDIR.get().resolve(ModInfo.MODID + "-client.toml")); // Register ourselves for server and other game events we are interested in diff --git a/java/squeek/appleskin/ModConfig.java b/java/squeek/appleskin/ModConfig.java index 6184528..47672c2 100644 --- a/java/squeek/appleskin/ModConfig.java +++ b/java/squeek/appleskin/ModConfig.java @@ -3,6 +3,7 @@ import com.electronwill.nightconfig.core.file.CommentedFileConfig; import com.electronwill.nightconfig.core.io.WritingMode; import net.minecraftforge.common.ForgeConfigSpec; +import net.minecraftforge.fml.event.config.ModConfigEvent; import java.nio.file.Path; import java.util.Arrays; @@ -145,5 +146,52 @@ public static void init(Path file) BUILDER.pop(); } + public static int[] SATURATION_HUD_OVERLAY_COLORS_CACHE = SATURATION_HUD_OVERLAY_COLORS_DEFAULT.stream().mapToInt(color -> { + try + { + return Integer.decode(color); + } + catch (NumberFormatException e) + { + return 0xFFD500; // fallback (yellow) + } + }).toArray(); + + public static void onConfigReloading(ModConfigEvent.Reloading event) + { + if (event.getConfig().getSpec() == SPEC) + { + SATURATION_HUD_OVERLAY_COLORS_CACHE = SATURATION_HUD_OVERLAY_COLORS.get().stream().mapToInt(color -> { + try + { + return Integer.decode(color); + } + catch (NumberFormatException e) + { + AppleSkin.Log.warn("Invalid color value in config: {}", color); + return 0xFFD500; // fallback (yellow) + } + }).toArray(); + } + } + + public static void onConfigLoading(ModConfigEvent.Loading event) + { + if (event.getConfig().getSpec() == SPEC) + { + SATURATION_HUD_OVERLAY_COLORS_CACHE = SATURATION_HUD_OVERLAY_COLORS.get().stream().mapToInt(color -> { + try + { + return Integer.decode(color); + } + catch (NumberFormatException e) + { + AppleSkin.Log.warn("Invalid color value in config: {}", color); + return 0xFFD500; // fallback (yellow) + } + }).toArray(); + } + } + public static final ForgeConfigSpec SPEC = BUILDER.build(); } diff --git a/java/squeek/appleskin/client/HUDOverlayHandler.java b/java/squeek/appleskin/client/HUDOverlayHandler.java index 8be6d8e..54b47d0 100644 --- a/java/squeek/appleskin/client/HUDOverlayHandler.java +++ b/java/squeek/appleskin/client/HUDOverlayHandler.java @@ -3,9 +3,7 @@ import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.FastColor; import net.minecraft.world.Difficulty; import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.LivingEntity; @@ -21,8 +19,6 @@ import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import org.lwjgl.opengl.GL11; -import org.slf4j.Logger; -import squeek.appleskin.AppleSkin; import squeek.appleskin.ModConfig; import squeek.appleskin.api.event.FoodValuesEvent; import squeek.appleskin.api.event.HUDOverlayEvent; @@ -32,11 +28,8 @@ import squeek.appleskin.helpers.TextureHelper; import squeek.appleskin.util.IntPoint; -import java.util.ArrayList; -import java.util.List; import java.util.Random; import java.util.Vector; -import java.util.stream.Collectors; @OnlyIn(Dist.CLIENT) public class HUDOverlayHandler @@ -46,9 +39,6 @@ public class HUDOverlayHandler private static byte alphaDir = 1; protected static int foodIconsOffset; - private static List lastSaturationHudOverlayColorsConfig; - private static int[] cachedSaturationHudOverlayColors; - public static final Vector healthBarOffsets = new Vector<>(); public static final Vector foodBarOffsets = new Vector<>(); @@ -234,23 +224,7 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio float saturationPerLayer = 20f; float saturationPerBar = saturationPerLayer / maxBars; - List currentColorsConfig = ModConfig.SATURATION_HUD_OVERLAY_COLORS.get(); - if (cachedSaturationHudOverlayColors == null || !currentColorsConfig.equals(lastSaturationHudOverlayColorsConfig)) - { - cachedSaturationHudOverlayColors = currentColorsConfig.stream().mapToInt(color -> { - try - { - return Integer.decode(color); - } - catch (NumberFormatException e) - { - AppleSkin.Log.warn("Invalid color value in config: {}", color); - return 0xFFD500; // fallback (yellow) - } - }).toArray(); - lastSaturationHudOverlayColorsConfig = new ArrayList<>(currentColorsConfig); - } - int[] colors = cachedSaturationHudOverlayColors; + int[] colors = ModConfig.SATURATION_HUD_OVERLAY_COLORS_CACHE; int fullLayers = (int) (totalSaturation / saturationPerLayer); float remainder = totalSaturation % saturationPerLayer; From d5958bea670ed98ce9bb53abb374812ef8035ee3 Mon Sep 17 00:00:00 2001 From: Mysticpasta1 Date: Tue, 10 Mar 2026 13:46:17 -0500 Subject: [PATCH 10/11] improve performance for saturation --- gradle.properties | 2 +- .../appleskin/client/HUDOverlayHandler.java | 36 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/gradle.properties b/gradle.properties index 2f2de43..655884f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,4 +11,4 @@ org.gradle.daemon=false # Mod Properties maven_group = squeek.appleskin archives_base_name = appleskin-forge -mod_version = 2.6.1 +mod_version = 2.6.2 diff --git a/java/squeek/appleskin/client/HUDOverlayHandler.java b/java/squeek/appleskin/client/HUDOverlayHandler.java index 54b47d0..63ffa00 100644 --- a/java/squeek/appleskin/client/HUDOverlayHandler.java +++ b/java/squeek/appleskin/client/HUDOverlayHandler.java @@ -223,34 +223,37 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio int maxBars = 10; float saturationPerLayer = 20f; float saturationPerBar = saturationPerLayer / maxBars; - int[] colors = ModConfig.SATURATION_HUD_OVERLAY_COLORS_CACHE; int fullLayers = (int) (totalSaturation / saturationPerLayer); float remainder = totalSaturation % saturationPerLayer; - // Draw all full layers - for (int layer = 0; layer < fullLayers; layer++) + // FIX: Only draw the top-most full layer. + // Drawing layers 0 through fullLayers-2 is a waste of GPU cycles. + if (fullLayers > 0) { - int color = (int) alpha | colors[layer % colors.length]; + // If there is a remainder, the "full" layer below it is at index fullLayers - 1 + // If there is NO remainder, the top-most full layer is also fullLayers - 1 + int topFullLayerIndex = fullLayers - 1; + + int color = (int) alpha | colors[topFullLayerIndex % colors.length]; float r = ((color >> 16) & 255) / 255f; float g = ((color >> 8) & 255) / 255f; float b = (color & 255) / 255f; float a = ((color >> 24) & 255) / 255f; guiGraphics.setColor(r, g, b, a); + // Draw only the 10 bars for this specific layer for (int i = 0; i < maxBars; i++) { IntPoint offset = foodBarOffsets.get(i); - if (offset == null) continue; - - int x = right + offset.x; - int y = top + offset.y; - guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, 7 * iconSize, 0, iconSize, iconSize); + if (offset != null) { + guiGraphics.blit(TextureHelper.MOD_ICONS, right + offset.x, top + offset.y, 7 * iconSize, 0, iconSize, iconSize); + } } } - // Draw partial layer if there is remaining saturation + // Draw the partial layer (the very top) if (remainder > 0) { int color = (int) alpha | colors[fullLayers % colors.length]; @@ -266,21 +269,18 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio for (int i = 0; i < fullBars; i++) { IntPoint offset = foodBarOffsets.get(i); - if (offset == null) continue; - int x = right + offset.x; - int y = top + offset.y; - guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, 7 * iconSize, 0, iconSize, iconSize); + if (offset != null) { + guiGraphics.blit(TextureHelper.MOD_ICONS, right + offset.x, top + offset.y, 7 * iconSize, 0, iconSize, iconSize); + } } - if (lastBarFraction > 0) + if (lastBarFraction > 0 && fullBars < maxBars) { IntPoint offset = foodBarOffsets.get(fullBars); if (offset != null) { - int x = right + offset.x; - int y = top + offset.y; int u = lastBarFraction >= 1.5f ? 6 * iconSize : lastBarFraction >= 1f ? 5 * iconSize : lastBarFraction > .5f ? 4 * iconSize : 0; - guiGraphics.blit(TextureHelper.MOD_ICONS, x, y, u, 0, iconSize, iconSize); + guiGraphics.blit(TextureHelper.MOD_ICONS, right + offset.x, top + offset.y, u, 0, iconSize, iconSize); } } } From 446e0967029a1efa72bb9adcb987ae1de1cc567d Mon Sep 17 00:00:00 2001 From: Mysticpasta1 Date: Wed, 11 Mar 2026 00:39:56 -0500 Subject: [PATCH 11/11] fix saturation not rendering + new number total sat layerss and config for it --- gradle.properties | 2 +- java/squeek/appleskin/ModConfig.java | 9 +++++++++ .../appleskin/client/HUDOverlayHandler.java | 19 +++++++++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 655884f..0ae3bf9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,4 +11,4 @@ org.gradle.daemon=false # Mod Properties maven_group = squeek.appleskin archives_base_name = appleskin-forge -mod_version = 2.6.2 +mod_version = 2.7.0 diff --git a/java/squeek/appleskin/ModConfig.java b/java/squeek/appleskin/ModConfig.java index 47672c2..0d1faf0 100644 --- a/java/squeek/appleskin/ModConfig.java +++ b/java/squeek/appleskin/ModConfig.java @@ -50,6 +50,12 @@ public static void init(Path file) private static final String SHOW_SATURATION_OVERLAY_COMMENT = "If true, shows your current saturation level overlayed on the hunger bar"; + public static final ForgeConfigSpec.BooleanValue SHOW_SATURATION_TEXT_OVERLAY; + public static boolean SHOW_SATURATION_TEXT_OVERLAY_DEFAULT = true; + private static final String SHOW_SATURATION_TEXT_OVERLAY_NAME = "showSaturationTextHudOverlay"; + private static final String SHOW_SATURATION_TEXT_OVERLAY_COMMENT = + "If true, shows the numerical value of your current saturation level"; + public static final ForgeConfigSpec.BooleanValue SHOW_FOOD_VALUES_OVERLAY; public static boolean SHOW_FOOD_VALUES_OVERLAY_DEFAULT = true; private static final String SHOW_FOOD_VALUES_OVERLAY_NAME = "showFoodValuesHudOverlay"; @@ -119,6 +125,9 @@ public static void init(Path file) SHOW_SATURATION_OVERLAY = BUILDER .comment(SHOW_SATURATION_OVERLAY_COMMENT) .define(SHOW_SATURATION_OVERLAY_NAME, SHOW_SATURATION_OVERLAY_DEFAULT); + SHOW_SATURATION_TEXT_OVERLAY = BUILDER + .comment(SHOW_SATURATION_TEXT_OVERLAY_COMMENT) + .define(SHOW_SATURATION_TEXT_OVERLAY_NAME, SHOW_SATURATION_TEXT_OVERLAY_DEFAULT); SHOW_FOOD_VALUES_OVERLAY = BUILDER .comment(SHOW_FOOD_VALUES_OVERLAY_COMMENT) .define(SHOW_FOOD_VALUES_OVERLAY_NAME, SHOW_FOOD_VALUES_OVERLAY_DEFAULT); diff --git a/java/squeek/appleskin/client/HUDOverlayHandler.java b/java/squeek/appleskin/client/HUDOverlayHandler.java index 63ffa00..a454d0c 100644 --- a/java/squeek/appleskin/client/HUDOverlayHandler.java +++ b/java/squeek/appleskin/client/HUDOverlayHandler.java @@ -223,6 +223,7 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio int maxBars = 10; float saturationPerLayer = 20f; float saturationPerBar = saturationPerLayer / maxBars; + int[] colors = ModConfig.SATURATION_HUD_OVERLAY_COLORS_CACHE; int fullLayers = (int) (totalSaturation / saturationPerLayer); @@ -236,7 +237,7 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio // If there is NO remainder, the top-most full layer is also fullLayers - 1 int topFullLayerIndex = fullLayers - 1; - int color = (int) alpha | colors[topFullLayerIndex % colors.length]; + int color = ((int) (alpha * 255) << 24) | colors[topFullLayerIndex % colors.length]; float r = ((color >> 16) & 255) / 255f; float g = ((color >> 8) & 255) / 255f; float b = (color & 255) / 255f; @@ -256,7 +257,7 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio // Draw the partial layer (the very top) if (remainder > 0) { - int color = (int) alpha | colors[fullLayers % colors.length]; + int color = ((int) (alpha * 255) << 24) | colors[fullLayers % colors.length]; float r = ((color >> 16) & 255) / 255f; float g = ((color >> 8) & 255) / 255f; float b = (color & 255) / 255f; @@ -286,6 +287,20 @@ public static void drawSaturationOverlay(float saturationGained, float saturatio } guiGraphics.setColor(1.0F, 1.0F, 1.0F, 1.0F); + + if (ModConfig.SHOW_SATURATION_TEXT_OVERLAY.get()) + { + String text; + if(totalSaturation % 20 == 0) { + text = "" + fullLayers; + } else { + text = "" + (fullLayers + 1); + } + int x = right + 2; + int y = top + 1; + int textColor = ((int) (alpha * 255) << 24) | 0xFFFFFF; + guiGraphics.drawString(mc.font, text, x, y, textColor, true); + } } public static void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, GuiGraphics guiGraphics, int right, int top, float alpha, boolean useRottenTextures)