diff --git a/README.md b/README.md index 521dd2b..7757ab9 100644 --- a/README.md +++ b/README.md @@ -20,20 +20,20 @@ To access the settings menu you will need: This mod can be used as a dependency via [Jitpack](https://jitpack.io/#Provismet/ProviHealth). Example entrypoint usage: -This entrypoint initialiser is what the mod uses to set its own values for the vanilla Entity Groups. +This entrypoint initialiser is what the mod uses to set its own values for vanilla entity tags. ```java public class SelfApiHook implements ProviHealthApi { @Override public void onInitialize () { - this.registerIcon(EntityGroup.AQUATIC, Items.COD); - this.registerIcon(EntityGroup.ARTHROPOD, Items.COBWEB); - this.registerIcon(EntityGroup.ILLAGER, Items.IRON_AXE); - this.registerIcon(EntityGroup.UNDEAD, Items.ROTTEN_FLESH); - - this.registerPortrait(EntityGroup.AQUATIC, ProviHealthClient.identifier("textures/gui/healthbars/aquatic.png")); - this.registerPortrait(EntityGroup.ARTHROPOD, ProviHealthClient.identifier("textures/gui/healthbars/arthropod.png")); - this.registerPortrait(EntityGroup.ILLAGER, ProviHealthClient.identifier("textures/gui/healthbars/illager.png")); - this.registerPortrait(EntityGroup.UNDEAD, ProviHealthClient.identifier("textures/gui/healthbars/undead.png")); + this.registerIcon(EntityTypeTags.AQUATIC, Items.COD, DEFAULT_PRIORITY + 1); + this.registerIcon(EntityTypeTags.ARTHROPOD, Items.COBWEB, DEFAULT_PRIORITY + 2); + this.registerIcon(EntityTypeTags.ILLAGER, Items.IRON_AXE, DEFAULT_PRIORITY); + this.registerIcon(EntityTypeTags.UNDEAD, Items.ROTTEN_FLESH, DEFAULT_PRIORITY + 3); + + this.registerPortrait(EntityTypeTags.AQUATIC, ProviHealthClient.identifier("textures/gui/healthbars/aquatic.png"), DEFAULT_PRIORITY + 1); + this.registerPortrait(EntityTypeTags.ARTHROPOD, ProviHealthClient.identifier("textures/gui/healthbars/arthropod.png"), DEFAULT_PRIORITY + 2); + this.registerPortrait(EntityTypeTags.ILLAGER, ProviHealthClient.identifier("textures/gui/healthbars/illager.png"), DEFAULT_PRIORITY); + this.registerPortrait(EntityTypeTags.UNDEAD, ProviHealthClient.identifier("textures/gui/healthbars/undead.png"), DEFAULT_PRIORITY + 3); } } diff --git a/src/main/java/com/provismet/provihealth/api/ProviHealthApi.java b/src/main/java/com/provismet/provihealth/api/ProviHealthApi.java index 2200fac..c851bc7 100644 --- a/src/main/java/com/provismet/provihealth/api/ProviHealthApi.java +++ b/src/main/java/com/provismet/provihealth/api/ProviHealthApi.java @@ -1,11 +1,11 @@ package com.provismet.provihealth.api; +import net.minecraft.registry.tag.TagKey; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import com.provismet.provihealth.hud.BorderRegistry; -import net.minecraft.entity.EntityGroup; import net.minecraft.entity.EntityType; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -20,12 +20,12 @@ public interface ProviHealthApi { * * The icon takes the form of an item. * - * @param entityGroup The entity group. + * @param tag The entity type tag. * @param item The item to serve as the icon. - * @return Whether or not the registry succeeded. This is false if the entity group already has an icon. + * @return Whether or not the registry succeeded. This is false if a higher priority icon already exists. */ - public default boolean registerIcon (EntityGroup entityGroup, @NotNull Item item) { - return this.registerIcon(entityGroup, item, false); + public default boolean registerIcon (TagKey> tag, @NotNull Item item) { + return this.registerIcon(tag, item, 0); } /** @@ -34,12 +34,12 @@ public default boolean registerIcon (EntityGroup entityGroup, @NotNull Item item * * The icon takes the form of an item. * - * @param entityGroup The entity group. + * @param tag The entity type tag. * @param item The item to serve as the icon. - * @return Whether or not the registry succeeded. Expect this to be true when force = true. + * @return Whether or not the registry succeeded. This is false if a higher priority icon already exists. */ - public default boolean registerIcon (EntityGroup entityGroup, @NotNull Item item, boolean force) { - return BorderRegistry.registerItem(entityGroup, item.getDefaultStack(), force); + public default boolean registerIcon (TagKey> tag, @NotNull Item item, int priority) { + return BorderRegistry.registerItem(tag, item.getDefaultStack(), priority); } /** @@ -50,10 +50,10 @@ public default boolean registerIcon (EntityGroup entityGroup, @NotNull Item item * * @param type The entity type. * @param item The item that will act as the icon for this entity type. - * @return Whether or not the registry succeeded. This is false if the entity type already has an icon. + * @return Whether or not the registry succeeded. This is false if a higher priority icon already exists. */ public default boolean registerIcon (EntityType type, @NotNull Item item) { - return this.registerIcon(type, item, false); + return this.registerIcon(type, item, 0); } /** @@ -64,11 +64,11 @@ public default boolean registerIcon (EntityType type, @NotNull Item item) { * * @param type The entity type. * @param item The item that will act as the icon for this entity type. - * @param force Whether or not this registration should override any pre-existing icon for the same entity type. - * @return Whether or not the registry succeeded. Expect this to be true when force = true. + * @param priority The priority level of this registration. Higher priority icons will override lower priority icons. + * @return Whether or not the registry succeeded. This is false if a higher priority icon already exists. */ - public default boolean registerIcon (EntityType type, @NotNull Item item, boolean force) { - return BorderRegistry.registerItem(type, item.getDefaultStack(), force); + public default boolean registerIcon (EntityType type, @NotNull Item item, int priority) { + return BorderRegistry.registerItem(type, item.getDefaultStack(), priority); } @@ -82,10 +82,10 @@ public default boolean registerIcon (EntityType type, @NotNull Item item, boo * * @param type The entity type. * @param item The item stack that will act as the icon for this entity type. - * @return Whether or not the registry succeeded. This is false if the entity type already has an icon. + * @return Whether or not the registry succeeded. This is false if a higher priority icon already exists. */ public default boolean registerIconStack (EntityType type, @Nullable ItemStack item) { - return this.registerIconStack(type, item, false); + return this.registerIconStack(type, item, 0); } /** @@ -98,11 +98,11 @@ public default boolean registerIconStack (EntityType type, @Nullable ItemStac * * @param type The entity type. * @param item The item stack that will act as the icon for this entity type. - * @param force Whether or not this registration should override any pre-existing icon for the same entity type. - * @return Whether or not the registry succeeded. Expect this to be true when force = true. + * @param priority The priority level of this registration. Higher priority icons will override lower priority icons. + * @return Whether or not the registry succeeded. This is false if a higher priority icon already exists. */ - public default boolean registerIconStack (EntityType type, @Nullable ItemStack item, boolean force) { - return BorderRegistry.registerItem(type, item, force); + public default boolean registerIconStack (EntityType type, @Nullable ItemStack item, int priority) { + return BorderRegistry.registerItem(type, item, priority); } /** @@ -115,10 +115,10 @@ public default boolean registerIconStack (EntityType type, @Nullable ItemStac * * @param type The entity group. * @param item The item stack that will act as the icon for this entity group. - * @return Whether or not the registry succeeded. This is false if the entity group already has an icon. + * @return Whether or not the registry succeeded. This is false if a higher priority icon already exists. */ - public default boolean registerIconStack (EntityGroup type, @Nullable ItemStack item) { - return this.registerIconStack(type, item, false); + public default boolean registerIconStack (TagKey> type, @Nullable ItemStack item) { + return this.registerIconStack(type, item, 0); } /** @@ -131,11 +131,11 @@ public default boolean registerIconStack (EntityGroup type, @Nullable ItemStack * * @param type The entity group. * @param item The item stack that will act as the icon for this entity group. - * @param force Whether or not this registration should override any pre-existing icon for the same entity group. - * @return Whether or not the registry succeeded. Expect this to be true when force = true. + * @param priority The priority level of this registration. Higher priority icons will override lower priority icons. + * @return Whether or not the registry succeeded. This is false if a higher priority icon already exists. */ - public default boolean registerIconStack (EntityGroup type, @Nullable ItemStack item, boolean force) { - return BorderRegistry.registerItem(type, item, force); + public default boolean registerIconStack (TagKey> type, @Nullable ItemStack item, int priority) { + return BorderRegistry.registerItem(type, item, priority); } /** @@ -147,12 +147,12 @@ public default boolean registerIconStack (EntityGroup type, @Nullable ItemStack * If your mod introduces new entity groups, use this method to define a portrait for them. * The image for the frame must be 96x48 in size. With the foreground (48x48) on the left and the background (48x48) on the right. * - * @param entityGroup The entity group. + * @param tag The entity group. * @param resource A full resource path (modid:textures/gui/etc/file.png) to the texture. - * @return Whether or not the registry succeeded. This is false if the entity group already has a portrait. + * @return Whether or not the registry succeeded. This is false if a higher priority portrait already exists. */ - public default boolean registerPortrait (EntityGroup entityGroup, @Nullable Identifier resource) { - return this.registerPortrait(entityGroup, resource, false); + public default boolean registerPortrait (TagKey> tag, @Nullable Identifier resource) { + return this.registerPortrait(tag, resource, 0); } /** @@ -166,11 +166,11 @@ public default boolean registerPortrait (EntityGroup entityGroup, @Nullable Iden * * @param entityGroup The entity group. * @param resource A full resource path (modid:textures/gui/etc/file.png) to the texture. - * @param force Whether or not this registration should override any pre-existing portrait for the same entity group. - * @return Whether or not the registry succeeded. Expect this to be true when force = true. + * @param priority The priority level of this registration. Higher priority portraits will override lower priority portraits. + * @return Whether or not the registry succeeded. This is false if a higher priority portrait already exists. */ - public default boolean registerPortrait (EntityGroup entityGroup, @Nullable Identifier resource, boolean force) { - return BorderRegistry.registerBorder(entityGroup, resource, force); + public default boolean registerPortrait (TagKey> entityGroup, @Nullable Identifier resource, int priority) { + return BorderRegistry.registerBorder(entityGroup, resource, priority); } /** @@ -185,10 +185,10 @@ public default boolean registerPortrait (EntityGroup entityGroup, @Nullable Iden * * @param type The entity type. * @param resource A full resource path (modid:textures/gui/etc/file.png) to the texture. - * @return Whether or not the registry succeeded. This is false if the entity type already has a portrait. + * @return Whether or not the registry succeeded. This is false if a higher priority portrait already exists. */ public default boolean registerPortrait (EntityType type, @Nullable Identifier resource) { - return this.registerPortrait(type, resource, false); + return this.registerPortrait(type, resource, 0); } /** @@ -203,10 +203,10 @@ public default boolean registerPortrait (EntityType type, @Nullable Identifie * * @param type The entity type. * @param resource A full resource path (modid:textures/gui/etc/file.png) to the texture. - * @param force Whether or not this registration should override any pre-existing portrait for the same entity type. - * @return Whether or not the registry succeeded. Expect this to be true when force = true. + * @param priority The priority level of this registration. Higher priority portraits will override lower priority portraits. + * @return Whether or not the registry succeeded. This is false if a higher priority portrait already exists. */ - public default boolean registerPortrait (EntityType type, @Nullable Identifier resource, boolean force) { - return BorderRegistry.registerBorder(type, resource, force); + public default boolean registerPortrait (EntityType type, @Nullable Identifier resource, int priority) { + return BorderRegistry.registerBorder(type, resource, priority); } } diff --git a/src/main/java/com/provismet/provihealth/compat/SelfApiHook.java b/src/main/java/com/provismet/provihealth/compat/SelfApiHook.java index 3a27808..8a4ac13 100644 --- a/src/main/java/com/provismet/provihealth/compat/SelfApiHook.java +++ b/src/main/java/com/provismet/provihealth/compat/SelfApiHook.java @@ -3,21 +3,23 @@ import com.provismet.provihealth.ProviHealthClient; import com.provismet.provihealth.api.ProviHealthApi; -import net.minecraft.entity.EntityGroup; +import net.minecraft.entity.EntityType; import net.minecraft.item.Items; +import net.minecraft.registry.tag.EntityTypeTags; public class SelfApiHook implements ProviHealthApi { + private static final int DEFAULT_PRIORITY = -999; + @Override public void onInitialize () { - this.registerIcon(EntityGroup.AQUATIC, Items.COD); - this.registerIcon(EntityGroup.ARTHROPOD, Items.COBWEB); - this.registerIcon(EntityGroup.ILLAGER, Items.IRON_AXE); - this.registerIcon(EntityGroup.UNDEAD, Items.ROTTEN_FLESH); + this.registerIcon(EntityTypeTags.AQUATIC, Items.COD, DEFAULT_PRIORITY + 1); + this.registerIcon(EntityTypeTags.ARTHROPOD, Items.COBWEB, DEFAULT_PRIORITY + 2); + this.registerIcon(EntityTypeTags.ILLAGER, Items.IRON_AXE, DEFAULT_PRIORITY); + this.registerIcon(EntityTypeTags.UNDEAD, Items.ROTTEN_FLESH, DEFAULT_PRIORITY + 3); - this.registerPortrait(EntityGroup.AQUATIC, ProviHealthClient.identifier("textures/gui/healthbars/aquatic.png")); - this.registerPortrait(EntityGroup.ARTHROPOD, ProviHealthClient.identifier("textures/gui/healthbars/arthropod.png")); - this.registerPortrait(EntityGroup.ILLAGER, ProviHealthClient.identifier("textures/gui/healthbars/illager.png")); - this.registerPortrait(EntityGroup.UNDEAD, ProviHealthClient.identifier("textures/gui/healthbars/undead.png")); + this.registerPortrait(EntityTypeTags.AQUATIC, ProviHealthClient.identifier("textures/gui/healthbars/aquatic.png"), DEFAULT_PRIORITY + 1); + this.registerPortrait(EntityTypeTags.ARTHROPOD, ProviHealthClient.identifier("textures/gui/healthbars/arthropod.png"), DEFAULT_PRIORITY + 2); + this.registerPortrait(EntityTypeTags.ILLAGER, ProviHealthClient.identifier("textures/gui/healthbars/illager.png"), DEFAULT_PRIORITY); + this.registerPortrait(EntityTypeTags.UNDEAD, ProviHealthClient.identifier("textures/gui/healthbars/undead.png"), DEFAULT_PRIORITY + 3); } - } diff --git a/src/main/java/com/provismet/provihealth/config/Options.java b/src/main/java/com/provismet/provihealth/config/Options.java index 4d8ad43..dbda09c 100644 --- a/src/main/java/com/provismet/provihealth/config/Options.java +++ b/src/main/java/com/provismet/provihealth/config/Options.java @@ -1,6 +1,6 @@ package com.provismet.provihealth.config; -import net.fabricmc.fabric.api.tag.convention.v1.ConventionalEntityTypeTags; +import net.fabricmc.fabric.api.tag.convention.v2.ConventionalEntityTypeTags; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; @@ -91,7 +91,6 @@ public class Options { public static boolean compatInHUD = false; public static HUDPortraitCompatMode HUDCompat = HUDPortraitCompatMode.STANDARD; - @SuppressWarnings("resource") public static boolean shouldRenderHealthFor (LivingEntity livingEntity) { if (blacklist.contains(EntityType.getId(livingEntity.getType()).toString())) return false; if (livingEntity.distanceTo(MinecraftClient.getInstance().player) > Options.maxRenderDistance) return false; @@ -454,8 +453,6 @@ else if (livingEntity.hasVehicle()) { return false; case ALWAYS_SHOW: - return true; - default: return true; } diff --git a/src/main/java/com/provismet/provihealth/hud/BorderRegistry.java b/src/main/java/com/provismet/provihealth/hud/BorderRegistry.java index 97d2d00..24c730b 100644 --- a/src/main/java/com/provismet/provihealth/hud/BorderRegistry.java +++ b/src/main/java/com/provismet/provihealth/hud/BorderRegistry.java @@ -2,98 +2,122 @@ import java.util.HashMap; +import net.minecraft.registry.tag.TagKey; import org.jetbrains.annotations.Nullable; import com.provismet.provihealth.ProviHealthClient; import com.provismet.provihealth.config.Options; -import net.minecraft.entity.EntityGroup; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemStack; import net.minecraft.util.Identifier; public class BorderRegistry { - private static final HashMap groupBorders = new HashMap<>(); - private static final HashMap groupItems = new HashMap<>(); - private static final HashMap, Identifier> overrideBorders = new HashMap<>(); - private static final HashMap, ItemStack> overrideItems = new HashMap<>(); + private static final HashMap, Identifier> borderCache = new HashMap<>(); + private static final HashMap, ItemStack> iconCache = new HashMap<>(); + private static final HashMap>, BorderPriority> tagBorderPriorities = new HashMap<>(); + private static final HashMap>, ItemPriority> tagIconPriorities = new HashMap<>(); + private static final HashMap, BorderPriority> typeBorderPriorities = new HashMap<>(); + private static final HashMap, ItemPriority> typeIconPriorities = new HashMap<>(); private static final Identifier DEFAULT = ProviHealthClient.identifier("textures/gui/healthbars/default.png"); - public static boolean registerBorder (EntityGroup group, @Nullable Identifier border, boolean force) { - if (group == null) { + public static boolean registerBorder (TagKey> entityTag, @Nullable Identifier border, int priority) { + if (entityTag == null) { ProviHealthClient.LOGGER.error("Attempted to register a null object to the border registry."); return false; } - else if (groupBorders.containsKey(group) && !force) { - return false; - } - else { - groupBorders.put(group, border); + else if (tagBorderPriorities.containsKey(entityTag) && priority <= tagBorderPriorities.get(entityTag).priority()) { return false; } + tagBorderPriorities.put(entityTag, new BorderPriority(border, priority)); + return true; } - public static boolean registerItem (EntityGroup group, @Nullable ItemStack item, boolean force) { - if (group == null) { + public static boolean registerItem (TagKey> entityTag, @Nullable ItemStack item, int priority) { + if (entityTag == null) { ProviHealthClient.LOGGER.error("Attempted to register a null EntityGroup to the icon registry."); return false; } - else if (groupItems.containsKey(group) && !force) { + else if (tagIconPriorities.containsKey(entityTag) && priority <= tagIconPriorities.get(entityTag).priority()) { return false; } - else { - groupItems.put(group, item); - return true; - } + tagIconPriorities.put(entityTag, new ItemPriority(item, priority)); + return true; } - public static boolean registerBorder (EntityType type, @Nullable Identifier border, boolean force) { + public static boolean registerBorder (EntityType type, @Nullable Identifier border, int priority) { if (type == null) { ProviHealthClient.LOGGER.error("Attempted to register a null EntityType to the border registry."); return false; } - else if (overrideBorders.containsKey(type) && !force) { + else if (typeBorderPriorities.containsKey(type) && priority <= typeBorderPriorities.get(type).priority()) { return false; } - else { - overrideBorders.put(type, border); - return true; - } + typeBorderPriorities.put(type, new BorderPriority(border, priority)); + return true; } - public static boolean registerItem (EntityType type, @Nullable ItemStack item, boolean force) { + public static boolean registerItem (EntityType type, @Nullable ItemStack item, int priority) { if (type == null) { ProviHealthClient.LOGGER.error("Attempted to register a null EntityType to the icon registry."); return false; } - else if (overrideItems.containsKey(type) && !force) { + else if (typeIconPriorities.containsKey(type) && priority <= typeIconPriorities.get(type).priority()) { return false; } - else { - overrideItems.put(type, item); - return true; - } + typeIconPriorities.put(type, new ItemPriority(item, priority)); + return true; } public static Identifier getBorder (@Nullable LivingEntity entity) { if (entity == null || !Options.useCustomHudPortraits) return DEFAULT; else { - Identifier border = null; - if (overrideBorders.containsKey(entity.getType())) border = overrideBorders.get(entity.getType()); - else if (groupBorders.containsKey(entity.getGroup())) border = groupBorders.get(entity.getGroup()); + if (borderCache.containsKey(entity.getType())) return borderCache.get(entity.getType()); - if (border == null) return DEFAULT; - else return border; + int maxPriority = -1000; + Identifier bestBorder = DEFAULT; + for (TagKey> entityTag : tagBorderPriorities.keySet()) { + if (entity.getType().isIn(entityTag) && tagBorderPriorities.get(entityTag).priority() > maxPriority) { + bestBorder = tagBorderPriorities.get(entityTag).borderId(); + maxPriority = tagBorderPriorities.get(entityTag).priority(); + } + } + for (EntityType type : typeBorderPriorities.keySet()) { + if (entity.getType() == type && typeBorderPriorities.get(type).priority() > maxPriority) { + bestBorder = typeBorderPriorities.get(type).borderId(); + maxPriority = typeBorderPriorities.get(type).priority(); + } + } + borderCache.put(entity.getType(), bestBorder); + return bestBorder; } } @Nullable public static ItemStack getItem (LivingEntity entity) { if (entity == null) return null; - else if (overrideItems.containsKey(entity.getType())) return overrideItems.get(entity.getType()); - else if (groupItems.containsKey(entity.getGroup())) return groupItems.get(entity.getGroup()); - else return null; + else if (iconCache.containsKey(entity.getType())) return iconCache.get(entity.getType()); + + int maxPriority = -1000; + ItemStack bestIcon = null; + for (TagKey> entityTag : tagIconPriorities.keySet()) { + if (entity.getType().isIn(entityTag) && tagIconPriorities.get(entityTag).priority() > maxPriority) { + bestIcon = tagIconPriorities.get(entityTag).itemStack(); + maxPriority = tagIconPriorities.get(entityTag).priority(); + } + } + for (EntityType type : typeIconPriorities.keySet()) { + if (entity.getType() == type && typeIconPriorities.get(type).priority() > maxPriority) { + bestIcon = typeIconPriorities.get(type).itemStack(); + maxPriority = typeIconPriorities.get(type).priority(); + } + } + iconCache.put(entity.getType(), bestIcon); + return bestIcon; } + + private record ItemPriority (ItemStack itemStack, int priority) {} + private record BorderPriority (Identifier borderId, int priority) {} } diff --git a/src/main/java/com/provismet/provihealth/hud/TargetHealthBar.java b/src/main/java/com/provismet/provihealth/hud/TargetHealthBar.java index ac3dbdd..7d87b7a 100644 --- a/src/main/java/com/provismet/provihealth/hud/TargetHealthBar.java +++ b/src/main/java/com/provismet/provihealth/hud/TargetHealthBar.java @@ -66,7 +66,7 @@ public void onHudRender (DrawContext drawContext, float tickDelta) { if (this.healthBarDuration > 0f) this.healthBarDuration -= tickDelta; else this.reset(); - if (!MinecraftClient.isHudEnabled() || MinecraftClient.getInstance().options.debugEnabled || MinecraftClient.getInstance().player.isSpectator()) return; + if (!MinecraftClient.isHudEnabled() || MinecraftClient.getInstance().getDebugHud().shouldShowDebugHud() || MinecraftClient.getInstance().player.isSpectator()) return; boolean isNew = false; diff --git a/src/main/java/com/provismet/provihealth/mixin/EntityRendererMixin.java b/src/main/java/com/provismet/provihealth/mixin/EntityRendererMixin.java index 7ba001f..068816f 100644 --- a/src/main/java/com/provismet/provihealth/mixin/EntityRendererMixin.java +++ b/src/main/java/com/provismet/provihealth/mixin/EntityRendererMixin.java @@ -20,7 +20,7 @@ @Mixin(EntityRenderer.class) public abstract class EntityRendererMixin { @Inject(method="renderLabelIfPresent", at=@At("HEAD"), cancellable=true) - private void cancelLabel (Entity entity, Text text, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, CallbackInfo info) { + private void cancelLabel (Entity entity, Text text, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, float tickDelta, CallbackInfo info) { if (TargetHealthBar.disabledLabels || (Options.overrideLabels && entity instanceof LivingEntity living && Options.shouldRenderHealthFor(living))) info.cancel(); } diff --git a/src/main/java/com/provismet/provihealth/mixin/LivingEntityMixin.java b/src/main/java/com/provismet/provihealth/mixin/LivingEntityMixin.java index 6df1ed0..f9b9b35 100644 --- a/src/main/java/com/provismet/provihealth/mixin/LivingEntityMixin.java +++ b/src/main/java/com/provismet/provihealth/mixin/LivingEntityMixin.java @@ -61,7 +61,7 @@ private void spawnParticles (CallbackInfo info) { if (this.prevHealth == -404.404f) this.prevHealth = this.getMaxHealth(); final Entity cameraEntity = MinecraftClient.getInstance().getCameraEntity(); - if (cameraEntity != null && (LivingEntity)(Object)this != cameraEntity && this.distanceTo(MinecraftClient.getInstance().getCameraEntity()) <= Options.maxParticleDistance) { + if (cameraEntity != null && this != cameraEntity && this.distanceTo(MinecraftClient.getInstance().getCameraEntity()) <= Options.maxParticleDistance) { if (this.getHealth() < this.prevHealth && Options.spawnDamageParticles) { this.getWorld().addParticle(new TextParticleEffect(Options.unpackedDamage, Options.damageAlpha, Options.particleScale, Options.damageParticleTextColour, String.format("%d", (int)this.prevHealth - (int)this.getHealth())), this.getX(), this.getEyeY(), this.getZ(), 0f, 0f, 0f); } diff --git a/src/main/java/com/provismet/provihealth/mixin/ParticleManagerMixin.java b/src/main/java/com/provismet/provihealth/mixin/ParticleManagerMixin.java deleted file mode 100644 index 2a3c4f2..0000000 --- a/src/main/java/com/provismet/provihealth/mixin/ParticleManagerMixin.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.provismet.provihealth.mixin; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import com.llamalad7.mixinextras.sugar.Local; -import com.provismet.provihealth.config.Options; -import com.provismet.provihealth.particle.TextParticle; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer.TextLayerType; -import net.minecraft.client.particle.Particle; -import net.minecraft.client.particle.ParticleManager; -import net.minecraft.client.render.Camera; -import net.minecraft.client.render.LightmapTextureManager; -import net.minecraft.client.render.VertexConsumerProvider; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.Vec3d; - -@Mixin(ParticleManager.class) -public abstract class ParticleManagerMixin { - @SuppressWarnings("resource") - @Inject(method="renderParticles", at=@At(value="INVOKE", target="Lnet/minecraft/client/particle/Particle;buildGeometry(Lnet/minecraft/client/render/VertexConsumer;Lnet/minecraft/client/render/Camera;F)V", shift=At.Shift.AFTER)) - private void addText (MatrixStack matrices, VertexConsumerProvider.Immediate vertexConsumers, LightmapTextureManager lightmapTextureManager, Camera camera, float tickDelta, CallbackInfo info, @Local(ordinal=0) Particle particle) { - if (particle instanceof TextParticle textParticle) { - Vec3d cameraPos = camera.getPos(); - Vec3d particlePos = textParticle.getPos(); - Vec3d prevParticlePos = textParticle.getPrevPos(); - float dX = (float)(MathHelper.lerp((double)tickDelta, prevParticlePos.x, particlePos.x) - cameraPos.getX()); - float dY = (float)(MathHelper.lerp((double)tickDelta, prevParticlePos.y, particlePos.y) - cameraPos.getY()); - float dZ = (float)(MathHelper.lerp((double)tickDelta, prevParticlePos.z, particlePos.z) - cameraPos.getZ()); - - matrices.push(); - matrices.translate(dX, dY, dZ); - matrices.multiply(camera.getRotation()); - - float scaleSize = textParticle.getSize(tickDelta) / 6f; - matrices.scale(-scaleSize, -scaleSize, -scaleSize); - - MinecraftClient.getInstance().textRenderer.draw(textParticle.getText(), 0f, 0f, textParticle.getColour(), Options.particleTextShadow, matrices.peek().getPositionMatrix(), vertexConsumers, TextLayerType.POLYGON_OFFSET, 0, textParticle.getBrightness(tickDelta)); - matrices.pop(); - } - } -} diff --git a/src/main/java/com/provismet/provihealth/particle/Particles.java b/src/main/java/com/provismet/provihealth/particle/Particles.java index 6190dad..838620d 100644 --- a/src/main/java/com/provismet/provihealth/particle/Particles.java +++ b/src/main/java/com/provismet/provihealth/particle/Particles.java @@ -9,7 +9,7 @@ import net.minecraft.registry.Registry; public class Particles { - public static final ParticleType TEXT_PARTICLE = FabricParticleTypes.complex(TextParticleEffect.PARAMETERS_FACTORY); + public static final ParticleType TEXT_PARTICLE = FabricParticleTypes.complex(TextParticleEffect.CODEC, TextParticleEffect.PACKET_CODEC); public static void register () { Registry.register(Registries.PARTICLE_TYPE, ProviHealthClient.identifier("text_particle"), TEXT_PARTICLE); diff --git a/src/main/java/com/provismet/provihealth/particle/TextParticle.java b/src/main/java/com/provismet/provihealth/particle/TextParticle.java index 3264a1a..eba232c 100644 --- a/src/main/java/com/provismet/provihealth/particle/TextParticle.java +++ b/src/main/java/com/provismet/provihealth/particle/TextParticle.java @@ -5,15 +5,21 @@ import com.provismet.provihealth.config.Options.DamageParticleType; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; import net.minecraft.client.particle.Particle; import net.minecraft.client.particle.ParticleFactory; import net.minecraft.client.particle.ParticleTextureSheet; import net.minecraft.client.particle.SpriteBillboardParticle; import net.minecraft.client.particle.SpriteProvider; +import net.minecraft.client.render.Camera; import net.minecraft.client.render.LightmapTextureManager; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.world.ClientWorld; import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.Vec3d; +import org.joml.Quaternionf; public class TextParticle extends SpriteBillboardParticle { private final String text; @@ -97,6 +103,29 @@ public void tick () { } } + @Override + public void buildGeometry (VertexConsumer vertexConsumer, Camera camera, float tickDelta) { + super.buildGeometry(vertexConsumer, camera, tickDelta); + + Quaternionf quaternionf = camera.getRotation(); + Vec3d cameraPos = camera.getPos(); + float dX = (float)(MathHelper.lerp((double)tickDelta, this.prevPosX, this.x) - cameraPos.getX()); + float dY = (float)(MathHelper.lerp((double)tickDelta, this.prevPosY, this.y) - cameraPos.getY()); + float dZ = (float)(MathHelper.lerp((double)tickDelta, this.prevPosZ, this.z) - cameraPos.getZ()); + + // I stack-traced buildGeometry, this block replicates the MatrixStack and then moves the text to the right place. + MatrixStack matrices = new MatrixStack(); + //matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(camera.getPitch())); + //matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(camera.getYaw() + 180.0f)); + matrices.translate(dX, dY, dZ); + matrices.multiply(quaternionf); + + float scaleSize = this.getSize(tickDelta) / 6f; + matrices.scale(-scaleSize, -scaleSize, -scaleSize); + + MinecraftClient.getInstance().textRenderer.draw(this.text, 0f, 0f, this.textColour, Options.particleTextShadow, matrices.peek().getPositionMatrix(), MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers(), TextRenderer.TextLayerType.POLYGON_OFFSET, 0, this.getBrightness(tickDelta)); + } + @Override public ParticleTextureSheet getType () { return ParticleTextureSheet.PARTICLE_SHEET_TRANSLUCENT; diff --git a/src/main/java/com/provismet/provihealth/particle/TextParticleEffect.java b/src/main/java/com/provismet/provihealth/particle/TextParticleEffect.java index 1b3d12d..25f9076 100644 --- a/src/main/java/com/provismet/provihealth/particle/TextParticleEffect.java +++ b/src/main/java/com/provismet/provihealth/particle/TextParticleEffect.java @@ -1,5 +1,15 @@ package com.provismet.provihealth.particle; +import com.mojang.datafixers.kinds.Applicative; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; +import net.minecraft.network.codec.PacketCodecs; +import net.minecraft.particle.DustParticleEffect; +import net.minecraft.util.dynamic.Codecs; import org.joml.Vector3f; import com.mojang.brigadier.StringReader; @@ -12,6 +22,16 @@ import net.minecraft.registry.Registries; public class TextParticleEffect implements ParticleEffect { + protected final static Codec TEXT_CODEC = Codec.string(1, 8).validate(text -> { + try { + Integer.valueOf(text); + return DataResult.success(text); + } + catch (Exception e) { + return DataResult.error(() -> "Text must be an integer: " + text); + } + }); + private final Vector3f colour; public final float alpha; @@ -19,6 +39,30 @@ public class TextParticleEffect implements ParticleEffect { public final String text; public final int textColour; + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> + instance.group( + Codecs.VECTOR_3F.fieldOf("colour").forGetter(effect -> effect.colour), + Codecs.POSITIVE_FLOAT.fieldOf("alpha").forGetter(effect -> effect.alpha), + Codecs.POSITIVE_FLOAT.fieldOf("scale").forGetter(effect -> effect.scale), + Codecs.rangedInt(0, 0xFFFFFF).fieldOf("text_colour").forGetter(effect -> effect.textColour), + TEXT_CODEC.fieldOf("text").forGetter(effect -> effect.text)) + .apply(instance, TextParticleEffect::new) + ); + + public static final PacketCodec PACKET_CODEC = PacketCodec.tuple( + PacketCodecs.VECTOR3F, + effect -> effect.colour, + PacketCodecs.FLOAT, + effect -> effect.alpha, + PacketCodecs.FLOAT, + effect -> effect.scale, + PacketCodecs.INTEGER, + effect -> effect.textColour, + PacketCodecs.string(8), + effect -> effect.text, + TextParticleEffect::new + ); + public TextParticleEffect (Vector3f colour, float alpha, float scale, int textColour, String text) { this.colour = colour; this.alpha = alpha; @@ -27,57 +71,10 @@ public TextParticleEffect (Vector3f colour, float alpha, float scale, int textCo this.textColour = textColour; } - @SuppressWarnings("deprecation") - public static final ParticleEffect.Factory PARAMETERS_FACTORY = new ParticleEffect.Factory() { - @Override - public TextParticleEffect read (ParticleType particleType, StringReader stringReader) throws CommandSyntaxException { - Vector3f colour = AbstractDustParticleEffect.readColor(stringReader); - stringReader.expect(' '); - float alpha = stringReader.readFloat(); - stringReader.expect(' '); - float scale = stringReader.readFloat(); - stringReader.expect(' '); - int textColour = stringReader.readInt(); - stringReader.expect(' '); - String text = stringReader.getRemaining(); - return new TextParticleEffect(colour, alpha, scale, textColour, text); - } - - @Override - public TextParticleEffect read (ParticleType type, PacketByteBuf buffer) { - return new TextParticleEffect(AbstractDustParticleEffect.readColor(buffer), buffer.readFloat(), buffer.readFloat(), buffer.readInt(), buffer.readString()); - } - }; - - @Override - public String asString () { - return String.format("%s %.2 %.2 %.2 %.2 %.2 %d %s", - Registries.PARTICLE_TYPE.getId(this.getType()), - this.colour.x(), - this.colour.y(), - this.colour.z(), - this.alpha, - this.scale, - this.textColour, - this.text - ); - } - @Override public ParticleType getType() { return Particles.TEXT_PARTICLE; } - - @Override - public void write (PacketByteBuf buffer) { - buffer.writeFloat(this.colour.x()); - buffer.writeFloat(this.colour.y()); - buffer.writeFloat(this.colour.z()); - buffer.writeFloat(this.alpha); - buffer.writeFloat(this.scale); - buffer.writeInt(this.textColour); - buffer.writeString(this.text); - } public Vector3f getColour () { return this.colour; diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 99f6090..343f629 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -26,7 +26,7 @@ "accessWidener": "provihealth.accesswidener", "depends": { "fabricloader": ">=0.15.0", - "minecraft": "~1.20 <1.20.2", + "minecraft": "~1.20.5", "java": ">=21", "fabric-api": "*", "lilylib": "*" diff --git a/src/main/resources/provihealth.mixins.json b/src/main/resources/provihealth.mixins.json index e3fc830..5eaa52a 100644 --- a/src/main/resources/provihealth.mixins.json +++ b/src/main/resources/provihealth.mixins.json @@ -6,8 +6,7 @@ ], "client": [ "LivingEntityMixin", - "EntityRendererMixin", - "ParticleManagerMixin" + "EntityRendererMixin" ], "injectors": { "defaultRequire": 1