Skip to content

Commit

Permalink
API can add text to HUD and health bars.
Browse files Browse the repository at this point in the history
  • Loading branch information
Provismet committed May 13, 2024
1 parent 33c08f9 commit 1a776fc
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import com.provismet.provihealth.api.ProviHealthApi;
import com.provismet.provihealth.config.Options;
import com.provismet.provihealth.hud.BorderRegistry;
import com.provismet.provihealth.hud.TargetHealthBar;
import com.provismet.provihealth.particle.Particles;

Expand Down Expand Up @@ -36,6 +37,7 @@ public void onInitializeClient () {
}
}
);
BorderRegistry.sortTitles();

Options.load();
Particles.register();
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/com/provismet/provihealth/api/ProviHealthApi.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.provismet.provihealth.api;

import net.minecraft.entity.LivingEntity;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.text.Text;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -209,4 +211,22 @@ public default boolean registerPortrait (EntityType<?> type, @Nullable Identifie
public default boolean registerPortrait (EntityType<?> type, @Nullable Identifier resource, int priority) {
return BorderRegistry.registerBorder(type, resource, priority);
}

/**
* Registers a function that places text on the healthbars or the HUD.
* Text can be used to place additional information about an entity on the HUD or the in-world healthbar.
* Text appears line-by-line on the HUD and in-world healthbar. Separate lines should be registered separately.
* The lambda is given context for whether it is appearing on the in-world or the HUD render.
*
* @param titleLambda Function of (LivingEntity entity, boolean isWorld, boolean isHUD) -> Text. Should return null if no text is wanted.
* @param order Determines the order of this line of text. Higher numbers appear at the top.
*/
public default void registerTitle (TitleGenerator titleLambda, int order) {
BorderRegistry.registerTitle(titleLambda, order);
}

@FunctionalInterface
public interface TitleGenerator {
public Text apply (LivingEntity entity, boolean isWorld, boolean isHUD);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ public static Screen build (Screen parent) {
.build()
);

hud.addEntry(entryBuilder.startBooleanToggle(Text.translatable("entry.provihealth.hudTitles"), Options.hudTitles)
.setDefaultValue(true)
.setTooltip(Text.translatable("tooltip.provihealth.hudTitles"))
.setSaveConsumer(newValue -> Options.hudTitles = newValue)
.build()
);

hud.addEntry(entryBuilder.startBooleanToggle(Text.translatable("entry.provihealth.gradient"), Options.hudGradient)
.setDefaultValue(false)
.setTooltip(Text.translatable("tooltip.provihealth.gradient"))
Expand Down Expand Up @@ -181,6 +188,13 @@ public static Screen build (Screen parent) {
.build()
);

health.addEntry(entryBuilder.startBooleanToggle(Text.translatable("entry.provihealth.worldTitles"), Options.worldTitles)
.setDefaultValue(true)
.setTooltip(Text.translatable("tooltip.provihealth.worldTitles"))
.setSaveConsumer(newValue -> Options.worldTitles = newValue)
.build()
);

health.addEntry(entryBuilder.startBooleanToggle(Text.translatable("entry.provihealth.gradient"), Options.worldGradient)
.setDefaultValue(false)
.setTooltip(Text.translatable("tooltip.provihealth.gradient"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.provismet.provihealth.ProviHealthClient;
import com.provismet.provihealth.api.ProviHealthApi;

import net.minecraft.entity.EntityType;
import net.minecraft.item.Items;
import net.minecraft.registry.tag.EntityTypeTags;

Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/provismet/provihealth/config/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class Options {
public static Vector3f unpackedStartHud = Vec3d.unpackRgb(hudStartColour).toVector3f();
public static Vector3f unpackedEndHud = Vec3d.unpackRgb(hudEndColour).toVector3f();
public static boolean hudGradient = false;
public static boolean hudTitles = true;

public static boolean showTextInWorld = true;
public static float maxRenderDistance = 24f;
Expand All @@ -70,6 +71,7 @@ public class Options {
public static boolean overrideLabels = false;
public static boolean worldShadows = true;
public static float worldOffsetY = 0f;
public static boolean worldTitles = true;

public static boolean spawnDamageParticles = true;
public static boolean spawnHealingParticles = false;
Expand Down Expand Up @@ -163,10 +165,12 @@ public static void save () {
.append("playerTarget", playersVisibilityOverride).newLine()
.append("otherHealth", others.name()).newLine()
.append("otherTarget", othersVisibilityOverride).newLine()
.append("worldTitles", worldTitles).newLine()
.append("bossHUD", bossHUD.name()).newLine()
.append("hostileHUD", hostileHUD.name()).newLine()
.append("playerHUD", playerHUD.name()).newLine()
.append("otherHUD", otherHUD.name()).newLine()
.append("hudTitles", hudTitles).newLine()
.append("damageParticles", spawnDamageParticles).newLine()
.append("healingParticles", spawnHealingParticles).newLine()
.append("damageColour", damageColour).newLine()
Expand Down Expand Up @@ -319,6 +323,10 @@ public static void load () {
playersVisibilityOverride = parser.nextBoolean();
break;

case "worldTitles":
worldTitles = parser.nextBoolean();
break;

case "playerHUD":
playerHUD = HUDType.valueOf(parser.nextString());
break;
Expand All @@ -335,6 +343,10 @@ public static void load () {
otherHUD = HUDType.valueOf(parser.nextString());
break;

case "hudTitles":
hudTitles = parser.nextBoolean();
break;

case "damageParticles":
spawnDamageParticles = parser.nextBoolean();
break;
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/provismet/provihealth/hud/BorderRegistry.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.provismet.provihealth.hud;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

import com.provismet.provihealth.api.ProviHealthApi;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;

import com.provismet.provihealth.ProviHealthClient;
Expand All @@ -21,6 +26,8 @@ public class BorderRegistry {
private static final HashMap<EntityType<?>, BorderPriority> typeBorderPriorities = new HashMap<>();
private static final HashMap<EntityType<?>, ItemPriority> typeIconPriorities = new HashMap<>();

private static final List<TitlePriority> orderedTitles = new ArrayList<>();

private static final Identifier DEFAULT = ProviHealthClient.identifier("textures/gui/healthbars/default.png");

public static boolean registerBorder (TagKey<EntityType<?>> entityTag, @Nullable Identifier border, int priority) {
Expand Down Expand Up @@ -71,6 +78,19 @@ else if (typeIconPriorities.containsKey(type) && priority <= typeIconPriorities.
return true;
}

public static void registerTitle (ProviHealthApi.TitleGenerator titleGen, int order) {
orderedTitles.add(new TitlePriority(titleGen, order));
}

public static void sortTitles () {
orderedTitles.sort(new Comparator<TitlePriority>() {
@Override
public int compare (TitlePriority a, TitlePriority b) {
return a.order() - b.order();
}
});
}

public static Identifier getBorder (@Nullable LivingEntity entity) {
if (entity == null || !Options.useCustomHudPortraits) return DEFAULT;
else {
Expand Down Expand Up @@ -118,6 +138,14 @@ public static ItemStack getItem (LivingEntity entity) {
return bestIcon;
}

public static List<Text> getTitle (LivingEntity entity, boolean world, boolean hud) {
if (entity == null) return null;

List<Text> titles = orderedTitles.stream().map(title -> title.titleGetter().apply(entity, world, hud)).filter(title -> title != null).toList();
return titles;
}

private record ItemPriority (ItemStack itemStack, int priority) {}
private record BorderPriority (Identifier borderId, int priority) {}
private record TitlePriority (ProviHealthApi.TitleGenerator titleGetter, int order) {}
}
26 changes: 25 additions & 1 deletion src/main/java/com/provismet/provihealth/hud/TargetHealthBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;

import java.util.List;

public class TargetHealthBar implements HudRenderCallback {
public static boolean disabledLabels = false;

Expand Down Expand Up @@ -149,9 +151,31 @@ public void onHudRender (DrawContext drawContext, float tickDelta) {

if (expectedLeftPixel < armourX) expectedLeftPixel = armourX + 10;

int mountHealthX = drawContext.drawText(MinecraftClient.getInstance().textRenderer, mountHealthString, expectedLeftPixel, BAR_Y + BAR_HEIGHT + (vehicleMaxHealthDeep > 0f ? MOUNT_BAR_HEIGHT : 0) + 2, 0xFFFFFF, true);
int mountHealthX = drawContext.drawText(MinecraftClient.getInstance().textRenderer, mountHealthString, expectedLeftPixel, BAR_Y + BAR_HEIGHT + MOUNT_BAR_HEIGHT + 2, 0xFFFFFF, true);
drawContext.drawTexture(MOUNT_HEART, mountHealthX, BAR_Y + BAR_HEIGHT + MOUNT_BAR_HEIGHT + 1, 9, 9, 0f, 0f, 9, 9, 9, 9);
}

// Render titles on HUD
if (Options.hudTitles) {
List<Text> titles = BorderRegistry.getTitle(this.target, false, true).reversed();

int titleX = 5;
int titleY = OFFSET_Y + FRAME_LENGTH + 5;

if (Options.hudPosition == HUDPosition.LEFT) {
for (Text title : titles) {
drawContext.drawText(MinecraftClient.getInstance().textRenderer, title, titleX, titleY, 0xFFFFFF, true);
titleY += 10;
}
}
else {
for (Text title : titles) {
titleX = MinecraftClient.getInstance().getWindow().getScaledWidth() - 10 - MinecraftClient.getInstance().textRenderer.getWidth(title);
drawContext.drawText(MinecraftClient.getInstance().textRenderer, title, titleX, titleY, 0xFFFFFF, true);
titleY += 10;
}
}
}
}

if (hudType != HUDType.NONE) {
Expand Down
62 changes: 46 additions & 16 deletions src/main/java/com/provismet/provihealth/world/EntityHealthBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.provismet.provihealth.ProviHealthClient;
import com.provismet.provihealth.config.Options;
import com.provismet.provihealth.config.Options.SeeThroughText;
import com.provismet.provihealth.hud.BorderRegistry;
import com.provismet.provihealth.interfaces.IMixinLivingEntity;
import com.provismet.provihealth.util.Visibility;

Expand All @@ -31,6 +32,8 @@
import net.minecraft.util.Identifier;
import net.minecraft.util.math.MathHelper;

import java.util.List;

public class EntityHealthBar {
private static final Identifier BARS = ProviHealthClient.identifier("textures/gui/healthbars/in_world.png");
private static final Identifier COMPAT_BARS = ProviHealthClient.identifier("textures/gui/healthbars/in_world_coloured.png");
Expand Down Expand Up @@ -107,46 +110,60 @@ public static void render (Entity entity, float tickDelta, MatrixStack matrices,
Matrix4f textModel = matrices.peek().getPositionMatrix();
final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
final String healthString = String.format("%d/%d", Math.round(target.getHealth()), Math.round(target.getMaxHealth()));
final float lineHeight = 9;

List<Text> titles = List.of(); // initialise an empty list
if (Options.worldTitles) titles = BorderRegistry.getTitle(target, true, false);

if (Options.overrideLabels) {
final Text targetName = getName(target);
final float targetNameWidth = textRenderer.getWidth(targetName);

float healthX = 50f - textRenderer.getWidth(healthString);
final float healthY = -9;
float nameX = -50;
float nameY = -9;
boolean wrapLines = targetNameWidth - 50f > healthX - 2f;
// 0 is in the centre.
final float leftmost = -50f;
final float rightmost = -leftmost;

float healthX = rightmost - textRenderer.getWidth(healthString);
final float healthY = -lineHeight;
float nameX = leftmost;
float nameY = -lineHeight;
boolean wrapLines = targetNameWidth - rightmost > healthX - 2f;

if (wrapLines) {
healthX = (healthX - 50) / 2f;
nameX = -targetNameWidth / 2f;
nameY -= 9;
nameY -= lineHeight;
}

if (target.shouldRenderName() && !target.isSneaky() && Options.seeThroughTextType != SeeThroughText.NONE) {
if ((target.shouldRenderName() || (target.hasCustomName() && target == MinecraftClient.getInstance().targetedEntity)) && !target.isSneaky() && Options.seeThroughTextType != SeeThroughText.NONE) {
if (Options.seeThroughTextType == SeeThroughText.STANDARD) {
if (Options.worldShadows) {
textRenderer.draw(targetName, nameX + 1, nameY + 1, 0x404040, false, textModel, vertexConsumers, TextLayerType.NORMAL, 0, light);
textRenderer.draw(healthString, healthX + 1, healthY + 1, 0x404040, false, textModel, vertexConsumers, TextLayerType.NORMAL, 0, light);
EntityHealthBar.renderFullText(textRenderer, targetName, healthString, titles, nameX + 1, nameY + 1, healthX + 1, healthY + 1, lineHeight, 1, 0x404040, false, textModel, vertexConsumers, TextLayerType.NORMAL, light);
}

matrices.translate(0, 0, 0.03f);
textModel = matrices.peek().getPositionMatrix();
textRenderer.draw(targetName, nameX, nameY, 0xFFFFFF, false, textModel, vertexConsumers, TextLayerType.SEE_THROUGH, 0, light);
textRenderer.draw(healthString, healthX, healthY, 0xFFFFFF, false, textModel, vertexConsumers, TextLayerType.SEE_THROUGH, 0, light);
EntityHealthBar.renderFullText(textRenderer, targetName, healthString, titles, nameX, nameY, healthX, healthY, lineHeight, 0, 0xFFFFFF, false, textModel, vertexConsumers, TextLayerType.SEE_THROUGH, light);
}
else {
textRenderer.draw(targetName, nameX, nameY, 0xFFFFFF, Options.worldShadows, textModel, vertexConsumers, TextLayerType.SEE_THROUGH, 0, light);
textRenderer.draw(healthString, healthX, healthY, 0xFFFFFF, Options.worldShadows, textModel, vertexConsumers, TextLayerType.SEE_THROUGH, 0, light);
EntityHealthBar.renderFullText(textRenderer, targetName, healthString, titles, nameX, nameY, healthX, healthY, lineHeight, 0, 0xFFFFFF, Options.worldShadows, textModel, vertexConsumers, TextLayerType.SEE_THROUGH, light);
}
}
else {
textRenderer.draw(targetName, nameX, nameY, 0xFFFFFF, Options.worldShadows, textModel, vertexConsumers, TextLayerType.NORMAL, 0, light);
textRenderer.draw(healthString, healthX, healthY, 0xFFFFFF, Options.worldShadows, textModel, vertexConsumers, TextLayerType.NORMAL, 0, light);
EntityHealthBar.renderFullText(textRenderer, targetName, healthString, titles, nameX, nameY, healthX, healthY, lineHeight, 0, 0xFFFFFF, Options.worldShadows, textModel, vertexConsumers, TextLayerType.NORMAL, light);
}
}
else {
textRenderer.draw(healthString, -(textRenderer.getWidth(healthString)) / 2f, -lineHeight, 0xFFFFFF, Options.worldShadows, textModel, vertexConsumers, TextLayerType.NORMAL, 0, light);

float titleX;
float titleY = -lineHeight;
for (Text title : titles) {
titleX = -textRenderer.getWidth(title) / 2f;
titleY -= lineHeight;
textRenderer.draw(title, titleX, titleY, 0xFFFFFF, Options.worldShadows, textModel, vertexConsumers, TextLayerType.NORMAL, 0, light);
}
}
else textRenderer.draw(healthString, -(textRenderer.getWidth(healthString)) / 2f, -10, 0xFFFFFF, Options.worldShadows, textModel, vertexConsumers, TextLayerType.NORMAL, 0, light);
matrices.pop();
}

Expand All @@ -155,6 +172,19 @@ public static void render (Entity entity, float tickDelta, MatrixStack matrices,
matrices.pop();
}

private static void renderFullText (TextRenderer textRenderer, Text name, String health, List<Text> titles, float nameX, float nameY, float healthX, float healthY, float titleLineHeight, float titleLineOffset, int colour, boolean shadow, Matrix4f model, VertexConsumerProvider vertexes, TextLayerType layerType, int light) {
textRenderer.draw(name, nameX, nameY, colour, shadow, model, vertexes, layerType, 0, light);
textRenderer.draw(health, healthX, healthY, colour, shadow, model, vertexes, layerType, 0, light);

float titleX;
float titleY = nameY;
for (Text title : titles) {
titleX = titleLineOffset - (textRenderer.getWidth(title) / 2f);
titleY -= titleLineHeight;
textRenderer.draw(title, titleX, titleY, colour, shadow, model, vertexes, layerType, 0, light);
}
}

@SuppressWarnings("resource")
private static Text getName (LivingEntity entity) {
if (entity instanceof PlayerEntity && entity.isInvisibleTo(MinecraftClient.getInstance().player)) return Text.translatable("entity.provihealth.unknownPlayer");
Expand Down
Loading

0 comments on commit 1a776fc

Please sign in to comment.