diff options
| author | olim88 <bobq4582@gmail.com> | 2025-03-03 04:07:43 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-03 12:07:43 +0800 |
| commit | e428f5aca33d42bf35f6ee0dc8edc8df2cc753b3 (patch) | |
| tree | 8348e7d8043e2cbe9fd99f2052bf11e808d848b8 | |
| parent | 9219939d0769b10f4ec504f57d98a60c24247185 (diff) | |
| download | Skyblocker-e428f5aca33d42bf35f6ee0dc8edc8df2cc753b3.tar.gz Skyblocker-e428f5aca33d42bf35f6ee0dc8edc8df2cc753b3.tar.bz2 Skyblocker-e428f5aca33d42bf35f6ee0dc8edc8df2cc753b3.zip | |
Health bars (#1028)
* basic features
working prototype for the feature
* add config and main features
* add option to show on mobs with only a health value
also adds tooltips for options
* clean up code and add more comments
* increase default size
* improve removing health code and incorrectly spell amour
* show when the nametag is shown
let it be rendered though walls if the nametag is being rendered
* add suggested changes
add fading between colours and fix problems
* Disable depth test and cleanup
* Migrate to oklab interpolation
* Fix health bar alpha
* Default off
---------
Co-authored-by: Kevinthegreat <92656833+kevinthegreat1@users.noreply.github.com>
8 files changed, 462 insertions, 19 deletions
diff --git a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java index 118d84e2..d076b7fd 100644 --- a/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java +++ b/src/main/java/de/hysky/skyblocker/config/categories/UIAndVisualsCategory.java @@ -610,6 +610,84 @@ public class UIAndVisualsCategory { .build() ) + //Custom Health bars + .group(OptionGroup.createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.healthBars")) + .collapsed(true) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.enabled")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.enabled.@Tooltip"))) + .binding(defaults.uiAndVisuals.healthBars.enabled, + () -> config.uiAndVisuals.healthBars.enabled, + newValue -> config.uiAndVisuals.healthBars.enabled = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Float>createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.scale")) + .binding(defaults.uiAndVisuals.healthBars.scale, + () -> config.uiAndVisuals.healthBars.scale, + newValue -> config.uiAndVisuals.healthBars.scale = newValue) + .controller(FloatFieldControllerBuilder::create) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.removeHealthFromName")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.removeHealthFromName.@Tooltip"))) + .binding(defaults.uiAndVisuals.healthBars.removeHealthFromName, + () -> config.uiAndVisuals.healthBars.removeHealthFromName, + newValue -> config.uiAndVisuals.healthBars.removeHealthFromName = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.removeMaxHealthFromName")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.removeMaxHealthFromName.@Tooltip"))) + .binding(defaults.uiAndVisuals.healthBars.removeMaxHealthFromName, + () -> config.uiAndVisuals.healthBars.removeMaxHealthFromName, + newValue -> config.uiAndVisuals.healthBars.removeMaxHealthFromName = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.applyToHealthOnlyMobs")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.applyToHealthOnlyMobs.@Tooltip"))) + .binding(defaults.uiAndVisuals.healthBars.applyToHealthOnlyMobs, + () -> config.uiAndVisuals.healthBars.applyToHealthOnlyMobs, + newValue -> config.uiAndVisuals.healthBars.applyToHealthOnlyMobs = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Boolean>createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.hideFullHealth")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.hideFullHealth.@Tooltip"))) + .binding(defaults.uiAndVisuals.healthBars.hideFullHealth, + () -> config.uiAndVisuals.healthBars.hideFullHealth, + newValue -> config.uiAndVisuals.healthBars.hideFullHealth = newValue) + .controller(ConfigUtils::createBooleanController) + .build()) + .option(Option.<Color>createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.fullBarColor")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.fullBarColor.@Tooltip"))) + .binding(defaults.uiAndVisuals.healthBars.fullBarColor, + () -> config.uiAndVisuals.healthBars.fullBarColor, + newValue -> config.uiAndVisuals.healthBars.fullBarColor = newValue) + .controller(ColorControllerBuilder::create) + .build()) + .option(Option.<Color>createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.halfBarColor")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.halfBarColor.@Tooltip"))) + .binding(defaults.uiAndVisuals.healthBars.halfBarColor, + () -> config.uiAndVisuals.healthBars.halfBarColor, + newValue -> config.uiAndVisuals.healthBars.halfBarColor = newValue) + .controller(ColorControllerBuilder::create) + .build()) + .option(Option.<Color>createBuilder() + .name(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.emptyBarColor")) + .description(OptionDescription.of(Text.translatable("skyblocker.config.uiAndVisuals.healthBars.emptyBarColor.@Tooltip"))) + .binding(defaults.uiAndVisuals.healthBars.emptyBarColor, + () -> config.uiAndVisuals.healthBars.emptyBarColor, + newValue -> config.uiAndVisuals.healthBars.emptyBarColor = newValue) + .controller(ColorControllerBuilder::create) + .build()) + .build() + ) + .build(); } diff --git a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java index 980a219a..90efa5d2 100644 --- a/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java +++ b/src/main/java/de/hysky/skyblocker/config/configs/UIAndVisualsConfig.java @@ -89,6 +89,9 @@ public class UIAndVisualsConfig { @SerialEntry public CompactDamage compactDamage = new CompactDamage(); + @SerialEntry + public HealthBars healthBars = new HealthBars(); + public static class ChestValue { @SerialEntry public boolean enableChestValue = true; @@ -417,4 +420,34 @@ public class UIAndVisualsConfig { @SerialEntry public Color critDamageGradientEnd = new Color(0xFF5555); } + + public static class HealthBars { + @SerialEntry + public boolean enabled = false; + + @SerialEntry + public float scale = 1.5f; + + @SerialEntry + public boolean removeHealthFromName = true; + + @SerialEntry + public boolean removeMaxHealthFromName = true; + + @SerialEntry + public boolean applyToHealthOnlyMobs = true; + + @SerialEntry + public boolean hideFullHealth = false; + + + @SerialEntry + public Color fullBarColor = new Color(0x00FF00); + + @SerialEntry + public Color halfBarColor = new Color(0xFF4600); + + @SerialEntry + public Color emptyBarColor = new Color(0xFF0000); + } } diff --git a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java index 0bc9a393..477b7adc 100644 --- a/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/de/hysky/skyblocker/mixins/ClientPlayNetworkHandlerMixin.java @@ -10,6 +10,7 @@ import de.hysky.skyblocker.config.configs.SlayersConfig; import de.hysky.skyblocker.config.configs.UIAndVisualsConfig; import de.hysky.skyblocker.skyblock.CompactDamage; import de.hysky.skyblocker.skyblock.FishingHelper; +import de.hysky.skyblocker.skyblock.HealthBars; import de.hysky.skyblocker.skyblock.SmoothAOTE; import de.hysky.skyblocker.skyblock.chocolatefactory.EggFinder; import de.hysky.skyblocker.skyblock.crimson.dojo.DojoManager; @@ -76,6 +77,7 @@ public abstract class ClientPlayNetworkHandlerMixin extends ClientCommonNetworkH EggFinder.checkIfEgg(armorStandEntity); CorpseFinder.checkIfCorpse(armorStandEntity); + HealthBars.heathBar(armorStandEntity); try { //Prevent packet handling fails if something goes wrong so that entity trackers still update, just without compact damage numbers CompactDamage.compactDamage(armorStandEntity); } catch (Exception e) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/HealthBars.java b/src/main/java/de/hysky/skyblocker/skyblock/HealthBars.java new file mode 100644 index 00000000..32f93040 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/skyblock/HealthBars.java @@ -0,0 +1,233 @@ +package de.hysky.skyblocker.skyblock; + +import de.hysky.skyblocker.annotations.Init; +import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.ColorUtils; +import de.hysky.skyblocker.utils.render.RenderHelper; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; +import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; +import org.apache.commons.lang3.StringUtils; + +import java.awt.*; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class HealthBars { + + private static final Identifier HEALTH_BAR_BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/sprites/boss_bar/white_background.png"); + private static final Identifier HEALTH_BAR_TEXTURE = Identifier.ofVanilla("textures/gui/sprites/boss_bar/white_progress.png"); + private static final Pattern HEALTH_PATTERN = Pattern.compile("(\\d{1,3}(,\\d{3})*(\\.\\d+)?)/(\\d{1,3}(,\\d{3})*(\\.\\d+)?)❤"); + private static final Pattern HEALTH_ONLY_PATTERN = Pattern.compile("(\\d{1,3}(,\\d{3})*(\\.\\d+)?)❤"); + + private static final Object2FloatOpenHashMap<ArmorStandEntity> healthValues = new Object2FloatOpenHashMap<>(); + private static final Object2IntOpenHashMap<ArmorStandEntity> mobStartingHealth = new Object2IntOpenHashMap<>(); + + @Init + public static void init() { + ClientPlayConnectionEvents.JOIN.register((_handler, _sender, _client) -> reset()); + WorldRenderEvents.AFTER_TRANSLUCENT.register(HealthBars::render); + ClientEntityEvents.ENTITY_UNLOAD.register(HealthBars::onEntityDespawn); + } + + private static void reset() { + healthValues.clear(); + mobStartingHealth.clear(); + } + + /** + * remove dead armor stands from health bars + * + * @param entity dying entity + */ + public static void onEntityDespawn(Entity entity, ClientWorld clientWorld) { + if (entity instanceof ArmorStandEntity armorStandEntity) { + healthValues.removeFloat(armorStandEntity); + mobStartingHealth.removeInt(armorStandEntity); + } + } + + /** + * Processes armorstand updates and if it's a mob with health get the value of its health and save it the hashmap + * + * @param armorStand updated armorstand + */ + public static void heathBar(ArmorStandEntity armorStand) { + if (!armorStand.isInvisible() || !armorStand.hasCustomName() || !armorStand.isCustomNameVisible() || !SkyblockerConfigManager.get().uiAndVisuals.healthBars.enabled) { + return; + } + + //check if armor stand is dead and remove it from list + if (armorStand.isDead()) { + healthValues.removeFloat(armorStand); + mobStartingHealth.removeInt(armorStand); + return; + } + + //check to see if the armor stand is a mob label with health + if (armorStand.getCustomName() == null) { + return; + } + Matcher healthMatcher = HEALTH_PATTERN.matcher(armorStand.getCustomName().getString()); + //if a health ratio can not be found send onto health only pattern + if (!healthMatcher.find()) { + healthOnlyCheck(armorStand); + return; + } + + //work out health value and save to hashMap + int firstValue = Integer.parseInt(healthMatcher.group(1).replace(",", "")); + int secondValue = Integer.parseInt(healthMatcher.group(4).replace(",", "")); + float health = (float) firstValue / secondValue; + healthValues.put(armorStand, health); + + //edit armor stand name to remove health + boolean removeValue = SkyblockerConfigManager.get().uiAndVisuals.healthBars.removeHealthFromName; + boolean removeMax = SkyblockerConfigManager.get().uiAndVisuals.healthBars.removeMaxHealthFromName; + //if both disabled no need to edit name + if (!removeValue && !removeMax) { + return; + } + MutableText cleanedText = Text.empty(); + List<Text> parts = armorStand.getCustomName().getSiblings(); + //loop though name and add every part to a new text skipping over the hidden health values + int healthStartIndex = -1; + for (int i = 0; i < parts.size(); i++) { + //remove value from name + if (i < parts.size() - 4 && StringUtils.join(parts.subList(i + 1, i + 5).stream().map(Text::getString).toArray(), "").equals(healthMatcher.group(0))) { + healthStartIndex = i; + } + if (healthStartIndex != -1) { + //skip parts of the health offset form staring index + switch (i - healthStartIndex) { + case 0 -> { // space before health + if (removeMax && removeValue) { + continue; + } + } + case 1 -> { // current health value + if (removeValue) { + continue; + } + } + case 2 -> { // "/" separating health values + if (removeMax) { + continue; + } + } + case 3 -> { // max health value + if (removeMax) { + continue; + } + } + case 4 -> { // "❤" at end of health + if (removeMax && removeValue) { + continue; + } + } + } + } + + cleanedText.append(parts.get(i)); + } + armorStand.setCustomName(cleanedText); + } + + /** + * Processes armor stands that only have a health value and no max health + * + * @param armorStand armorstand to check the name of + */ + private static void healthOnlyCheck(ArmorStandEntity armorStand) { + if (!SkyblockerConfigManager.get().uiAndVisuals.healthBars.applyToHealthOnlyMobs || armorStand.getCustomName() == null) { + return; + } + Matcher healthOnlyMatcher = HEALTH_ONLY_PATTERN.matcher(armorStand.getCustomName().getString()); + //if not found return + if (!healthOnlyMatcher.find()) { + return; + } + + //get the current health of the mob + int currentHealth = Integer.parseInt(healthOnlyMatcher.group(1).replace(",", "")); + + //if it's a new health only armor stand add to starting health lookup (not always full health if already damaged but best that can be done) + if (!mobStartingHealth.containsKey(armorStand)) { + mobStartingHealth.put(armorStand, currentHealth); + } + + //add to health bar values + float health = (float) currentHealth / mobStartingHealth.getInt(armorStand); + healthValues.put(armorStand, health); + + //if enabled remove from name + if (!SkyblockerConfigManager.get().uiAndVisuals.healthBars.removeHealthFromName) { + return; + } + MutableText cleanedText = Text.empty(); + List<Text> parts = armorStand.getCustomName().getSiblings(); + //loop though name and add every part to a new text skipping over the health value + for (int i = 0; i < parts.size(); i++) { + //skip space before value, value and heart from name + if (i < parts.size() - 2 && parts.subList(i + 1, i + 3).stream().map(Text::getString).collect(Collectors.joining()).equals(healthOnlyMatcher.group(0))) { + //skip the heart + i += 2; + continue; + } + cleanedText.append(parts.get(i)); + } + armorStand.setCustomName(cleanedText); + } + + /** + * Loops though armor stands with health bars and renders a bar for each of them just bellow the name label + * + * @param context render context + */ + private static void render(WorldRenderContext context) { + if (!SkyblockerConfigManager.get().uiAndVisuals.healthBars.enabled || healthValues.isEmpty()) { + return; + } + Color fullColor = SkyblockerConfigManager.get().uiAndVisuals.healthBars.fullBarColor; + Color halfColor = SkyblockerConfigManager.get().uiAndVisuals.healthBars.halfBarColor; + Color emptyColor = SkyblockerConfigManager.get().uiAndVisuals.healthBars.emptyBarColor; + boolean hideFullHealth = SkyblockerConfigManager.get().uiAndVisuals.healthBars.hideFullHealth; + float scale = SkyblockerConfigManager.get().uiAndVisuals.healthBars.scale; + float tickDelta = context.tickCounter().getTickDelta(false); + float width = scale; + float height = scale * 0.1f; + + for (Object2FloatMap.Entry<ArmorStandEntity> healthValue : healthValues.object2FloatEntrySet()) { + //if the health bar is full and the setting is enabled to hide it stop rendering it + float health = healthValue.getFloatValue(); + if (hideFullHealth && health == 1) { + continue; + } + + ArmorStandEntity armorStand = healthValue.getKey(); + //only render health bar if name is visible + if (!armorStand.shouldRenderName()) { + return; + } + //gets the mixed color of the health bar + int mixedColor = ColorUtils.interpolate(health, emptyColor.getRGB(), halfColor.getRGB(), fullColor.getRGB()); + float[] components = ColorUtils.getFloatComponents(mixedColor); + // Render the health bar texture with scaling based on health percentage + RenderHelper.renderTextureInWorld(context, armorStand.getCameraPosVec(tickDelta).add(0, 0.25 - height, 0), width, height, 1f, 1f, new Vec3d(width * -0.5f, 0, 0), HEALTH_BAR_BACKGROUND_TEXTURE, components, 1f, true); + RenderHelper.renderTextureInWorld(context, armorStand.getCameraPosVec(tickDelta).add(0, 0.25 - height, 0), width * health, height, health, 1f, new Vec3d(width * -0.5f, 0, 0.003f), HEALTH_BAR_TEXTURE, components, 1f, true); + } + } +} diff --git a/src/main/java/de/hysky/skyblocker/utils/ColorUtils.java b/src/main/java/de/hysky/skyblocker/utils/ColorUtils.java index a9ea6a9f..0a2ca7d8 100644 --- a/src/main/java/de/hysky/skyblocker/utils/ColorUtils.java +++ b/src/main/java/de/hysky/skyblocker/utils/ColorUtils.java @@ -1,6 +1,7 @@ package de.hysky.skyblocker.utils; import net.minecraft.util.DyeColor; +import net.minecraft.util.math.ColorHelper; import net.minecraft.util.math.MathHelper; public class ColorUtils { @@ -12,9 +13,9 @@ public class ColorUtils { */ public static float[] getFloatComponents(int color) { return new float[] { - ((color >> 16) & 0xFF) / 255f, - ((color >> 8) & 0xFF) / 255f, - (color & 0xFF) / 255f + ColorHelper.getRedFloat(color), + ColorHelper.getGreenFloat(color), + ColorHelper.getBlueFloat(color), }; } @@ -34,25 +35,37 @@ public class ColorUtils { } /** - * Interpolates linearly between two colours. + * Interpolates between two colours. */ - //Credit to https://codepen.io/OliverBalfour/post/programmatically-making-gradients public static int interpolate(int firstColor, int secondColor, double percentage) { - int r1 = MathHelper.square((firstColor >> 16) & 0xFF); - int g1 = MathHelper.square((firstColor >> 8) & 0xFF); - int b1 = MathHelper.square(firstColor & 0xFF); - - int r2 = MathHelper.square((secondColor >> 16) & 0xFF); - int g2 = MathHelper.square((secondColor >> 8) & 0xFF); - int b2 = MathHelper.square(secondColor & 0xFF); - - double inverse = 1d - percentage; - - int r3 = (int) Math.floor(Math.sqrt(r1 * inverse + r2 * percentage)); - int g3 = (int) Math.floor(Math.sqrt(g1 * inverse + g2 * percentage)); - int b3 = (int) Math.floor(Math.sqrt(b1 * inverse + b2 * percentage)); + return OkLabColor.interpolate(firstColor, secondColor, (float) percentage); + } - return (r3 << 16) | (g3 << 8 ) | b3; + /** + * Interpolates between multiple colors. + * + * @param percentage percentage between 0 and 1 + * @param colors the colors to interpolate between + * @return the interpolated color + * @see #interpolate(int, int, double) + */ + public static int interpolate(double percentage, int... colors) { + int colorCount = colors.length; + if (colorCount == 0) { + return 0; + } + if (colorCount == 1 || percentage <= 0) { + return colors[0]; + } + if (percentage >= 1) { + return colors[colorCount - 1]; + } + + double scaledPercentage = percentage * (colorCount - 1); + int index = (int) Math.floor(scaledPercentage); + double remainder = scaledPercentage - index; + + return interpolate(colors[index], colors[index + 1], remainder); } /** diff --git a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java index e203767a..b77ef21b 100644 --- a/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java +++ b/src/main/java/de/hysky/skyblocker/utils/render/RenderHelper.java @@ -292,6 +292,52 @@ public class RenderHelper { RenderSystem.disableDepthTest(); } + /** + * Renders a texture in world space facing the player (like a name tag) + * @param context world render context + * @param pos world position + * @param width rendered width + * @param height rendered height + * @param textureWidth amount of texture rendered width + * @param textureHeight amount of texture rendered height + * @param renderOffset offset once it's been placed in the world facing the player + * @param texture reference to texture to render + * @param shaderColor color to apply to the texture + * @param throughWalls if it should render though walls + */ + public static void renderTextureInWorld(WorldRenderContext context, Vec3d pos, float width, float height, float textureWidth, float textureHeight, Vec3d renderOffset, Identifier texture, float[] shaderColor, float alpha, boolean throughWalls) { + Matrix4f positionMatrix = new Matrix4f(); + Camera camera = context.camera(); + Vec3d cameraPos = camera.getPos(); + + positionMatrix + .translate((float) (pos.getX() - cameraPos.getX()), (float) (pos.getY() - cameraPos.getY()), (float) (pos.getZ() - cameraPos.getZ())) + .rotate(camera.getRotation()); + + Tessellator tessellator = RenderSystem.renderThreadTesselator(); + + RenderSystem.setShader(ShaderProgramKeys.POSITION_TEX); + RenderSystem.setShaderTexture(0, texture); + RenderSystem.setShaderColor(shaderColor[0], shaderColor[1], shaderColor[2], alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.disableCull(); + RenderSystem.depthFunc(throughWalls ? GL11.GL_ALWAYS : GL11.GL_LEQUAL); + BufferBuilder buffer = tessellator.begin(DrawMode.QUADS, VertexFormats.POSITION_TEXTURE); + + buffer.vertex(positionMatrix, (float) renderOffset.getX(), (float) renderOffset.getY(), (float) renderOffset.getZ()).texture(1, 1 - textureHeight); + buffer.vertex(positionMatrix, (float) renderOffset.getX(), (float) renderOffset.getY() + height, (float) renderOffset.getZ()).texture(1, 1); + buffer.vertex(positionMatrix, (float) renderOffset.getX() + width, (float) renderOffset.getY() + height , (float) renderOffset.getZ()).texture(1 - textureWidth, 1); + buffer.vertex(positionMatrix, (float) renderOffset.getX() + width, (float) renderOffset.getY(), (float) renderOffset.getZ()).texture(1 - textureWidth, 1 - textureHeight); + + BufferRenderer.drawWithGlobalProgram(buffer.end()); + + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + RenderSystem.enableCull(); + RenderSystem.depthFunc(GL11.GL_LEQUAL); + RenderSystem.disableDepthTest(); + } + public static void renderText(WorldRenderContext context, Text text, Vec3d pos, boolean throughWalls) { renderText(context, text, pos, 1, throughWalls); } diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index c3627f3c..0bb0fd0c 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -824,6 +824,25 @@ "skyblocker.config.uiAndVisuals.hideEmptyTooltips": "Hide empty item tooltips in menus", "skyblocker.config.uiAndVisuals.hideEmptyTooltips.@Tooltip": "Hides the tooltip of an item if it doesn't have any information to display. Also blocks clicks on filler items like glass panes.", + "skyblocker.config.uiAndVisuals.healthBars": "Custom Mob Health Bars", + "skyblocker.config.uiAndVisuals.healthBars.enabled": "Enabled", + "skyblocker.config.uiAndVisuals.healthBars.enabled.@Tooltip": "(only updates when health updates)", + "skyblocker.config.uiAndVisuals.healthBars.scale": "Scale", + "skyblocker.config.uiAndVisuals.healthBars.removeHealthFromName": "Remove Health From Name", + "skyblocker.config.uiAndVisuals.healthBars.removeHealthFromName.@Tooltip": "Remove the current health of mobs from their nametag", + "skyblocker.config.uiAndVisuals.healthBars.removeMaxHealthFromName": "Remove Max Health From Name", + "skyblocker.config.uiAndVisuals.healthBars.removeMaxHealthFromName.@Tooltip": "Remove the maximum health of mobs from their nametag", + "skyblocker.config.uiAndVisuals.healthBars.applyToHealthOnlyMobs": "Apply to Health Only Mobs", + "skyblocker.config.uiAndVisuals.healthBars.applyToHealthOnlyMobs.@Tooltip": "Also add health bars to mobs with only a current health value (will sometimes be wrong if mob is damaged before loading in)", + "skyblocker.config.uiAndVisuals.healthBars.hideFullHealth": "Hide Full Health Bars", + "skyblocker.config.uiAndVisuals.healthBars.hideFullHealth.@Tooltip": "Don't show the health bar when its full", + "skyblocker.config.uiAndVisuals.healthBars.fullBarColor": "Full Health bar Color", + "skyblocker.config.uiAndVisuals.healthBars.fullBarColor.@Tooltip": "Color of health bar when mob is at full hp", + "skyblocker.config.uiAndVisuals.healthBars.halfBarColor": "Half Health Bar Color", + "skyblocker.config.uiAndVisuals.healthBars.halfBarColor.@Tooltip": "Color of health bar when mob is at half hp", + "skyblocker.config.uiAndVisuals.healthBars.emptyBarColor": "Empty Health Bar Color", + "skyblocker.config.uiAndVisuals.healthBars.emptyBarColor.@Tooltip": "Color of health bar when mob is at full hp", + "skyblocker.config.uiAndVisuals.inputCalculator": "Input Calculator", "skyblocker.config.uiAndVisuals.inputCalculator.enabled": "Enable Sign Calculator", "skyblocker.config.uiAndVisuals.inputCalculator.enabled.@Tooltip": "Enables the ability for you to do calculations when inputting values such as price for the ah.\n Key:\n S = 64\n E = 160\n K = 1,000\n M = 1,000,000\n B = 1,000,000,000\n\n purse/P = current purse value", diff --git a/src/test/java/de/hysky/skyblocker/utils/ColorUtilsTest.java b/src/test/java/de/hysky/skyblocker/utils/ColorUtilsTest.java new file mode 100644 index 00000000..bd250511 --- /dev/null +++ b/src/test/java/de/hysky/skyblocker/utils/ColorUtilsTest.java @@ -0,0 +1,19 @@ +package de.hysky.skyblocker.utils; + +import net.minecraft.util.math.ColorHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class ColorUtilsTest { + @Test + void testFloatComponents() { + Assertions.assertArrayEquals(new float[]{0.2f, 0.4f, 0.6f}, ColorUtils.getFloatComponents(ColorHelper.getArgb(51, 102, 153))); + } + + @Test + void testInterpolate() { + Assertions.assertEquals(0x00FE00, ColorUtils.interpolate(0.5, 0xFF0000, 0x00FF00, 0x0000FF)); + Assertions.assertEquals(0xD0A800, ColorUtils.interpolate(0.25, 0xFF0000, 0x00FF00, 0x0000FF)); + Assertions.assertEquals(0x00A9BE, ColorUtils.interpolate(0.75, 0xFF0000, 0x00FF00, 0x0000FF)); + } +} |
