Skip to content

Commit

Permalink
Add status effects to HUD.
Browse files Browse the repository at this point in the history
Backport from 1.21.2+
  • Loading branch information
Provismet committed Jan 2, 2025
1 parent c6e4937 commit 2b6d809
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.provismet.provihealth;

import com.provismet.provihealth.util.StatusEffectIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -33,14 +34,15 @@ public void onInitializeClient () {
entrypoint.getEntrypoint().onInitialize();
}
catch (Exception e) {
LOGGER.error("Mod " + otherModId + " caused an error during inter-mod initialisation: ", e);
LOGGER.error("Mod {} caused an error during inter-mod initialisation: ", otherModId, e);
}
}
);
BorderRegistry.sortTitles();

Options.load();
Particles.register();
StatusEffectIdentifier.setup();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ public static Screen build (Screen parent) {
.build()
);

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

hud.addEntry(entryBuilder.startBooleanToggle(Text.translatable("entry.provihealth.gradient"), Options.hudGradient)
.setDefaultValue(false)
.setTooltip(Text.translatable("tooltip.provihealth.gradient"))
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/provismet/provihealth/config/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class Options {
public static Vector3f unpackedEndHud = Vec3d.unpackRgb(hudEndColour).toVector3f();
public static boolean hudGradient = false;
public static boolean hudTitles = true;
public static boolean hudStatuses = true;

public static boolean showTextInWorld = true;
public static float maxRenderDistance = 24f;
Expand Down Expand Up @@ -176,6 +177,7 @@ public static void save () {
.append("playerHUD", playerHUD.name()).newLine()
.append("otherHUD", otherHUD.name()).newLine()
.append("hudTitles", hudTitles).newLine()
.append("hudStatusEffects", hudStatuses).newLine()
.append("damageParticles", spawnDamageParticles).newLine()
.append("healingParticles", spawnHealingParticles).newLine()
.append("damageColour", damageColour).newLine()
Expand Down Expand Up @@ -360,6 +362,10 @@ public static void load () {
hudTitles = parser.nextBoolean();
break;

case "hudStatusEffects":
hudStatuses = parser.nextBoolean();
break;

case "damageParticles":
spawnDamageParticles = parser.nextBoolean();
break;
Expand Down Expand Up @@ -443,7 +449,7 @@ public static void load () {
break;

default:
ProviHealthClient.LOGGER.warn("Unknown label \"" + label + "\" found in config.");
ProviHealthClient.LOGGER.warn("Unknown label \"{}\" found in config.", label);
parser.skipValue();
break;
}
Expand Down
45 changes: 40 additions & 5 deletions src/main/java/com/provismet/provihealth/hud/TargetHealthBar.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package com.provismet.provihealth.hud;

import com.provismet.provihealth.interfaces.IMixinLivingEntity;
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.RenderTickCounter;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.texture.StatusEffectSpriteManager;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.registry.entry.RegistryEntry;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
Expand Down Expand Up @@ -50,6 +58,12 @@ public class TargetHealthBar implements HudRenderCallback {
private static int BAR_X = FRAME_LENGTH - 5;
private static int BAR_Y = OFFSET_Y + FRAME_LENGTH / 2 - (BAR_HEIGHT + MOUNT_BAR_HEIGHT) / 2;

private static int TEXT_BASE_Y = BAR_Y + BAR_HEIGHT + 1;

private static int EFFECT_X = FRAME_LENGTH + 2;
private static int EFFECT_BASE_Y = TEXT_BASE_Y + 11;
private static int EFFECT_X_OFFSET = 17;

private static final float BAR_V2 = ((float)BAR_HEIGHT / (float)(BAR_HEIGHT + MOUNT_BAR_HEIGHT)) / 2f; // Accounting for index.
private static final float MOUNT_BAR_U2 = (float)MOUNT_BAR_WIDTH / (float)BAR_WIDTH;
private static final float MOUNT_BAR_V1 = ((float)BAR_HEIGHT / (float)(BAR_HEIGHT + MOUNT_BAR_HEIGHT)) / 2f;
Expand Down Expand Up @@ -136,14 +150,15 @@ public void onHudRender (DrawContext drawContext, RenderTickCounter tickCounter)
}

// Render health value and heart icons
int offsetFromMountBar = (vehicleMaxHealthDeep > 0f ? MOUNT_BAR_HEIGHT : 0);
int healthX = drawContext.drawText(MinecraftClient.getInstance().textRenderer, String.format("%d/%d", Math.round(this.target.getHealth()), Math.round(this.target.getMaxHealth())), infoLeftX, BAR_Y + BAR_HEIGHT + (vehicleMaxHealthDeep > 0f ? MOUNT_BAR_HEIGHT : 0) + 2, 0xFFFFFF, true); // Health Value
drawContext.drawTexture(HEART, healthX, BAR_Y + BAR_HEIGHT + (vehicleMaxHealthDeep > 0f ? MOUNT_BAR_HEIGHT : 0) + 1, 9, 9, 0f, 0f, 9, 9, 9, 9);
drawContext.drawTexture(HEART, healthX, TEXT_BASE_Y + offsetFromMountBar, 9, 9, 0f, 0f, 9, 9, 9, 9);

// Render armour icon if necessary
int armourX = MinecraftClient.getInstance().textRenderer.getWidth(String.format("%d/%d", Math.round(this.target.getMaxHealth()), Math.round(this.target.getMaxHealth()))) + infoLeftX + 18;
if (this.target.getArmor() > 0) {
armourX = drawContext.drawText(MinecraftClient.getInstance().textRenderer, String.format("%d", this.target.getArmor()), armourX, BAR_Y + BAR_HEIGHT + (vehicleMaxHealthDeep > 0f ? MOUNT_BAR_HEIGHT : 0) + 2, 0xFFFFFF, true);
drawContext.drawTexture(ARMOUR, armourX, BAR_Y + BAR_HEIGHT + (vehicleMaxHealthDeep > 0f ? MOUNT_BAR_HEIGHT : 0) + 1, 9, 9, 0f, 0f, 9, 9, 9, 9);
armourX = drawContext.drawText(MinecraftClient.getInstance().textRenderer, String.format("%d", this.target.getArmor()), armourX, TEXT_BASE_Y + 1 + offsetFromMountBar, 0xFFFFFF, true);
drawContext.drawTexture(ARMOUR, armourX, TEXT_BASE_Y + offsetFromMountBar, 9, 9, 0f, 0f, 9, 9, 9, 9);
}

if (vehicleMaxHealthDeep > 0f) {
Expand All @@ -153,8 +168,22 @@ public void onHudRender (DrawContext drawContext, RenderTickCounter tickCounter)

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

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);
int mountHealthX = drawContext.drawText(MinecraftClient.getInstance().textRenderer, mountHealthString, expectedLeftPixel, TEXT_BASE_Y + 1 + MOUNT_BAR_HEIGHT, 0xFFFFFF, true);
drawContext.drawTexture(MOUNT_HEART, mountHealthX, TEXT_BASE_Y + MOUNT_BAR_HEIGHT, 9, 9, 0f, 0f, 9, 9, 9, 9);
}

if (Options.hudStatuses) {
List<RegistryEntry<StatusEffect>> effects = ((IMixinLivingEntity)this.target).provi_Health$getClientSideStatusEffects();

if (!effects.isEmpty()) {
StatusEffectSpriteManager statusEffectSpriteManager = MinecraftClient.getInstance().getStatusEffectSpriteManager();
int effectXOffset = 0;
for (RegistryEntry<StatusEffect> effect : effects) {
Sprite effectSprite = statusEffectSpriteManager.getSprite(effect);
drawContext.drawSprite(EFFECT_X + effectXOffset, EFFECT_BASE_Y + offsetFromMountBar, 0, 16, 16, effectSprite);
effectXOffset += EFFECT_X_OFFSET;
}
}
}

// Render titles on HUD
Expand Down Expand Up @@ -298,15 +327,21 @@ private void reset () {
private void adjustForScreenSize () {
OFFSET_Y = Math.min((int)(MinecraftClient.getInstance().getWindow().getScaledHeight() * (Options.hudOffsetPercent / 100f)), MinecraftClient.getInstance().getWindow().getScaledHeight() - FRAME_LENGTH);
BAR_Y = OFFSET_Y + FRAME_LENGTH / 2 - (BAR_HEIGHT + MOUNT_BAR_HEIGHT) / 2;
TEXT_BASE_Y = BAR_Y + BAR_HEIGHT + 1;
EFFECT_BASE_Y = TEXT_BASE_Y + 11;

if (Options.hudPosition == HUDPosition.LEFT) {
OFFSET_X = 0;
BAR_X = FRAME_LENGTH - 5;
EFFECT_X = FRAME_LENGTH + 2;
EFFECT_X_OFFSET = 17;
}
else {
int width = MinecraftClient.getInstance().getWindow().getScaledWidth();
OFFSET_X = width - FRAME_LENGTH;
BAR_X = OFFSET_X + 5 - BAR_WIDTH;
EFFECT_X = OFFSET_X - 18;
EFFECT_X_OFFSET = -17;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.provismet.provihealth.interfaces;

import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.registry.entry.RegistryEntry;

import java.util.List;

public interface IMixinLivingEntity {
public float provihealth_glideHealth (float glideFactor);

public float provihealth_glideVehicle (float trueValue, float glideFactor);

List<RegistryEntry<StatusEffect>> provi_Health$getClientSideStatusEffects ();
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package com.provismet.provihealth.mixin;

import com.provismet.provihealth.config.Options;
import com.provismet.provihealth.interfaces.IMixinLivingEntity;
import com.provismet.provihealth.util.StatusEffectIdentifier;
import net.minecraft.entity.data.TrackedData;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.particle.EntityEffectParticleEffect;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.registry.entry.RegistryEntry;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import com.provismet.provihealth.config.Options;
import com.provismet.provihealth.interfaces.IMixinLivingEntity;
import com.provismet.provihealth.particle.TextParticleEffect;

import net.minecraft.client.MinecraftClient;
Expand All @@ -18,12 +25,16 @@
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.World;

import java.util.Comparator;
import java.util.List;
import java.util.Objects;

@Mixin(LivingEntity.class)
public abstract class LivingEntityMixin extends Entity implements IMixinLivingEntity {
protected LivingEntityMixin (EntityType<?> type, World world) {
super(type, world);
}

@Unique
private float currentHealthPercent = 1f;

Expand All @@ -39,6 +50,8 @@ protected LivingEntityMixin (EntityType<?> type, World world) {
@Shadow
public abstract float getMaxHealth();

@Shadow @Final private static TrackedData<List<ParticleEffect>> POTION_SWIRLS;

@Override
public float provihealth_glideHealth (float glideFactor) {
float trueValue = MathHelper.clamp(this.getHealth() / this.getMaxHealth(), 0f, 1f);
Expand All @@ -56,6 +69,21 @@ public float provihealth_glideVehicle (float trueValue, float glideFactor) {
return this.currentVehicleHealthPercent;
}

@Override
public List<RegistryEntry<StatusEffect>> provi_Health$getClientSideStatusEffects () {
List<ParticleEffect> particles = this.dataTracker.get(POTION_SWIRLS);
if (particles.isEmpty()) return List.of();

return particles.stream()
.filter(particle -> particle instanceof EntityEffectParticleEffect)
.map(particle -> StatusEffectIdentifier.fromParticleEffect((EntityEffectParticleEffect)particle))
.distinct()
.filter(Objects::nonNull)
.sorted(Comparator.comparing(effect -> effect.value().getName().getString()))
.sorted(Comparator.comparingInt(effect -> effect.value().getCategory().ordinal()))
.toList();
}

@Inject(method="tick", at=@At("TAIL"))
private void spawnParticles (CallbackInfo info) {
if (this.prevHealth == -404.404f) this.prevHealth = this.getMaxHealth();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.provismet.provihealth.util;

import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.particle.EntityEffectParticleEffect;
import net.minecraft.registry.Registries;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.util.math.ColorHelper;

import java.util.HashMap;
import java.util.Map;

public abstract class StatusEffectIdentifier {
private static final Map<Integer, RegistryEntry<StatusEffect>> colourToEffect = new HashMap<>();

public static RegistryEntry<StatusEffect> fromParticleEffect (EntityEffectParticleEffect particleEffect) {
return colourToEffect.getOrDefault(particleEffect.color, null);
}

public static void setup () {
Registries.STATUS_EFFECT.streamEntries().forEach(effect -> colourToEffect.putIfAbsent(ColorHelper.Argb.fullAlpha(effect.value().getColor()), effect));
}
}
1 change: 1 addition & 0 deletions src/main/resources/assets/provihealth/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"entry.provihealth.damageAlpha": "Damage Particle Alpha",
"entry.provihealth.healingAlpha": "Healing Particle Alpha",
"entry.provihealth.compatHud": "HUD Paperdoll Render Mode",
"entry.provihealth.hudStatuses": "Show Status Effects",
"entry.provihealth.tintBackground": "Tint Background",
"entry.provihealth.teamColours": "Use Team Colors",

Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/provihealth.accesswidener
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
accessWidener v2 named

accessible method net/minecraft/client/gui/DrawContext drawTexturedQuad (Lnet/minecraft/util/Identifier;IIIIIFFFF)V
accessible method net/minecraft/client/gui/DrawContext drawTexturedQuad (Lnet/minecraft/util/Identifier;IIIIIFFFFFFFF)V
accessible method net/minecraft/client/gui/DrawContext drawTexturedQuad (Lnet/minecraft/util/Identifier;IIIIIFFFFFFFF)V
accessible field net/minecraft/particle/EntityEffectParticleEffect color I

0 comments on commit 2b6d809

Please sign in to comment.