From 66cac9b835e95ebf680e37976a8ef349bcbcf2ae Mon Sep 17 00:00:00 2001 From: A5ho9999 Date: Tue, 25 Mar 2025 13:01:39 +1030 Subject: [PATCH] Rainbow Effect --- .../data/ColorOverlayVertexConsumer.java | 55 ++++++++++++++ .../data/EntityRenderStateTracker.java | 12 +++ .../ix0rai/rainglow/data/RainbowManager.java | 74 +++++++++++++++++++ .../ix0rai/rainglow/data/RainglowEntity.java | 19 +++-- .../client/AllayEntityRendererMixin.java | 4 +- .../mixin/client/EntityRenderStateMixin.java | 12 +++ .../mixin/client/EntityRendererMixin.java | 8 ++ .../client/GlowSquidEntityRendererMixin.java | 4 +- .../client/LivingEntityRendererMixin.java | 48 ++++++++++++ .../client/SlimeEntityRendererMixin.java | 4 +- .../SlimeOverlayFeatureRendererMixin.java | 37 +++++++++- src/main/resources/rainglow.mixins.json | 1 + 12 files changed, 269 insertions(+), 9 deletions(-) create mode 100644 src/main/java/io/ix0rai/rainglow/data/ColorOverlayVertexConsumer.java create mode 100644 src/main/java/io/ix0rai/rainglow/data/RainbowManager.java create mode 100644 src/main/java/io/ix0rai/rainglow/mixin/client/LivingEntityRendererMixin.java diff --git a/src/main/java/io/ix0rai/rainglow/data/ColorOverlayVertexConsumer.java b/src/main/java/io/ix0rai/rainglow/data/ColorOverlayVertexConsumer.java new file mode 100644 index 0000000..cbc0404 --- /dev/null +++ b/src/main/java/io/ix0rai/rainglow/data/ColorOverlayVertexConsumer.java @@ -0,0 +1,55 @@ +package io.ix0rai.rainglow.data; + +import com.mojang.blaze3d.vertex.VertexConsumer; + +public class ColorOverlayVertexConsumer implements VertexConsumer { + private final VertexConsumer base; + private final float r; + private final float g; + private final float b; + + public ColorOverlayVertexConsumer(VertexConsumer base, float r, float g, float b) { + this.base = base; + this.r = r; + this.g = g; + this.b = b; + } + + // Vertex + @Override + public VertexConsumer xyz(float f, float g, float h) { + return base.xyz(f, g, h); + } + + @Override + public VertexConsumer color(int red, int green, int blue, int alpha) { + // Adjust Alpha value to stop my eyes from blowing out + // Change the float value to control transparency (For outer slime layer) + int adjustedAlpha = (int)(alpha * 0.9f); + + return base.color(Math.min(255, (int)(red * r)), Math.min(255, (int)(green * g)), Math.min(255, (int)(blue * b)), adjustedAlpha); + } + + // Texture + @Override + public VertexConsumer uv0(float f, float g) { + return base.uv0(f, g); + } + + // Overlay + @Override + public VertexConsumer uv1(int i, int j) { + return base.uv1(i, j); + } + + // Light + @Override + public VertexConsumer uv2(int i, int j) { + return base.uv2(i, j); + } + + @Override + public VertexConsumer normal(float x, float y, float z) { + return base.normal(x, y, z); + } +} diff --git a/src/main/java/io/ix0rai/rainglow/data/EntityRenderStateTracker.java b/src/main/java/io/ix0rai/rainglow/data/EntityRenderStateTracker.java index 9e9fdb5..80aac8c 100644 --- a/src/main/java/io/ix0rai/rainglow/data/EntityRenderStateTracker.java +++ b/src/main/java/io/ix0rai/rainglow/data/EntityRenderStateTracker.java @@ -20,4 +20,16 @@ public interface EntityRenderStateTracker { * @return The UUID of the associated entity, or null if not set */ UUID rainglow$getEntityUuid(); + + /** + * Set the Rainbow Rendering for the associated entity + * @param isRainbow boolean value to toggle the rainbow rendering + */ + void rainglow$setRainbow(boolean isRainbow); + + /** + * Get the Rainbow Rendering for the associated entity + * @return boolean for entity should have the rainbow rendering + */ + boolean rainglow$isRainbow(); } \ No newline at end of file diff --git a/src/main/java/io/ix0rai/rainglow/data/RainbowManager.java b/src/main/java/io/ix0rai/rainglow/data/RainbowManager.java new file mode 100644 index 0000000..446372e --- /dev/null +++ b/src/main/java/io/ix0rai/rainglow/data/RainbowManager.java @@ -0,0 +1,74 @@ +package io.ix0rai.rainglow.data; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; + +import java.util.UUID; + +// TODO: Might need the client environment tag, but should only run client side +public class RainbowManager { + public static LivingEntity findEntityByUuid(ClientWorld world, UUID uuid) { + for (Entity entity : world.getEntities()) { + if (entity.getUuid().equals(uuid)) { + return (LivingEntity) entity; + } + } + + return null; + } + + @SuppressWarnings("DataFlowIssue") + public static int getRainbowColor(UUID entityUuid) { + long worldTime = MinecraftClient.getInstance().world.getTime(); + + int hashCode = entityUuid.hashCode(); + float entityOffset = (hashCode & 0x7FFFFFFF) % 1000 / 1000.0f; + float smoothHue = getHue(worldTime, entityOffset); + + return smoothedHsvToRgb(smoothHue); + } + + public static float getHue(long worldTime, float entityOffset) { + // Change this value to increase or decrease transition time, lower is quicker + final int cycleDuration = 1000; + + // Progress within cycle (0.0 to 1.0) + float progress = (worldTime % cycleDuration) / (float)cycleDuration; + float smoothHue = (float)(0.5 + 0.5 * Math.sin(progress * 2 * Math.PI)); + + smoothHue = (smoothHue + entityOffset) % 1.0f; + return smoothHue; + } + + public static int smoothedHsvToRgb(float h) { + h = h % 1.0f; + if (h < 0) h += 1.0f; + + float hueSix = h * 6.0f; + int hueSixCategory = (int)hueSix; + float hueSixRemainder = hueSix - hueSixCategory; + + float p = 0.0f; + float q = (1.0f - (hueSixRemainder)); + float t = (1.0f - ((1.0f - hueSixRemainder))); + + float r, g, b; + + switch (hueSixCategory % 6) { + case 0: r = (float) 1.0; g = t; b = p; break; + case 1: r = q; g = (float) 1.0; b = p; break; + case 2: r = p; g = (float) 1.0; b = t; break; + case 3: r = p; g = q; b = (float) 1.0; break; + case 4: r = t; g = p; b = (float) 1.0; break; + default: r = (float) 1.0; g = p; b = q; break; + } + + int ri = Math.min(255, Math.max(0, (int)(r * 255))); + int gi = Math.min(255, Math.max(0, (int)(g * 255))); + int bi = Math.min(255, Math.max(0, (int)(b * 255))); + + return (0xFF << 24) | (ri << 16) | (gi << 8) | bi; + } +} diff --git a/src/main/java/io/ix0rai/rainglow/data/RainglowEntity.java b/src/main/java/io/ix0rai/rainglow/data/RainglowEntity.java index 7d5cdc0..102591f 100644 --- a/src/main/java/io/ix0rai/rainglow/data/RainglowEntity.java +++ b/src/main/java/io/ix0rai/rainglow/data/RainglowEntity.java @@ -102,20 +102,29 @@ public static RainglowEntity get(Entity entity) { } @Nullable - public Identifier overrideTexture(ClientWorld world, UUID uuid) { + public Identifier overrideTexture(ClientWorld world, UUID uuid, boolean rainbowState) { AtomicReference texture = new AtomicReference<>(); - world.getEntities().forEach(entity -> { + // Switch to a full statement so can break when uuid matches for slightly better performance and avoid unnecessary processing + + for (Entity entity : world.getEntities()) { if (entity.getUuid().equals(uuid)) { - texture.set(this.overrideTexture(entity)); + texture.set(this.overrideTexture(entity, rainbowState)); + break; } - }); + } return texture.get(); } // Return the override texture instead of applying through callback - public Identifier overrideTexture(Entity entity) { + public Identifier overrideTexture(Entity entity, boolean rainbowState) { + if (rainbowState) { + // Set the texture to light gray, best for overlaying colours as white is a bit too bright + Identifier texture = RainglowColour.LIGHT_GRAY.getTexture(this); + return texture != null ? texture : this.getDefaultTexture(); + } + RainglowColour colour = Rainglow.getColour(entity); // Returning null will just use default texture, no need for extra checks diff --git a/src/main/java/io/ix0rai/rainglow/mixin/client/AllayEntityRendererMixin.java b/src/main/java/io/ix0rai/rainglow/mixin/client/AllayEntityRendererMixin.java index 0a5563c..e9aa1d9 100644 --- a/src/main/java/io/ix0rai/rainglow/mixin/client/AllayEntityRendererMixin.java +++ b/src/main/java/io/ix0rai/rainglow/mixin/client/AllayEntityRendererMixin.java @@ -23,8 +23,10 @@ public void getTexture(class_9996 state, CallbackInfoReturnable cir) if (entityUuid != null) { ClientWorld world = MinecraftClient.getInstance().world; if (world != null) { + boolean rainbowState = ((EntityRenderStateTracker) state).rainglow$isRainbow(); + RainglowEntity type = RainglowEntity.ALLAY; - Identifier texture = type.overrideTexture(world, entityUuid); + Identifier texture = type.overrideTexture(world, entityUuid, rainbowState); cir.setReturnValue(texture != null ? texture : type.getDefaultTexture()); } } diff --git a/src/main/java/io/ix0rai/rainglow/mixin/client/EntityRenderStateMixin.java b/src/main/java/io/ix0rai/rainglow/mixin/client/EntityRenderStateMixin.java index 3c6628f..a999678 100644 --- a/src/main/java/io/ix0rai/rainglow/mixin/client/EntityRenderStateMixin.java +++ b/src/main/java/io/ix0rai/rainglow/mixin/client/EntityRenderStateMixin.java @@ -12,6 +12,8 @@ public class EntityRenderStateMixin implements EntityRenderStateTracker { @Unique private UUID entityUuid; + @Unique + private boolean isRainbow; @Override public void rainglow$setEntity(Entity entity) { @@ -26,4 +28,14 @@ public class EntityRenderStateMixin implements EntityRenderStateTracker { public UUID rainglow$getEntityUuid() { return this.entityUuid; } + + @Override + public void rainglow$setRainbow(boolean isRainbow) { + this.isRainbow = isRainbow; + } + + @Override + public boolean rainglow$isRainbow() { + return isRainbow; + } } \ No newline at end of file diff --git a/src/main/java/io/ix0rai/rainglow/mixin/client/EntityRendererMixin.java b/src/main/java/io/ix0rai/rainglow/mixin/client/EntityRendererMixin.java index d3c3778..d1e9d20 100644 --- a/src/main/java/io/ix0rai/rainglow/mixin/client/EntityRendererMixin.java +++ b/src/main/java/io/ix0rai/rainglow/mixin/client/EntityRendererMixin.java @@ -15,6 +15,14 @@ public class EntityRendererMixin { private void updateRenderState(T entity, S state, float f, CallbackInfo ci) { if (state instanceof EntityRenderStateTracker) { ((EntityRenderStateTracker) state).rainglow$setEntity(entity); + + // TODO: This is a placeholder, better to add a new variant. + if (entity.hasCustomName()) { + ((EntityRenderStateTracker) state).rainglow$setRainbow(entity.getCustomName().getString().contains("rainbow")); + } else { + // Reset it just in case + ((EntityRenderStateTracker) state).rainglow$setRainbow(false); + } } } } diff --git a/src/main/java/io/ix0rai/rainglow/mixin/client/GlowSquidEntityRendererMixin.java b/src/main/java/io/ix0rai/rainglow/mixin/client/GlowSquidEntityRendererMixin.java index d3d1921..97e52ad 100644 --- a/src/main/java/io/ix0rai/rainglow/mixin/client/GlowSquidEntityRendererMixin.java +++ b/src/main/java/io/ix0rai/rainglow/mixin/client/GlowSquidEntityRendererMixin.java @@ -27,8 +27,10 @@ public void getTexture(class_10069 state, CallbackInfoReturnable cir if (entityUuid != null) { ClientWorld world = MinecraftClient.getInstance().world; if (world != null) { + boolean rainbowState = ((EntityRenderStateTracker) state).rainglow$isRainbow(); + RainglowEntity type = RainglowEntity.GLOW_SQUID; - Identifier texture = type.overrideTexture(world, entityUuid); + Identifier texture = type.overrideTexture(world, entityUuid, rainbowState); cir.setReturnValue(texture != null ? texture : type.getDefaultTexture()); } } diff --git a/src/main/java/io/ix0rai/rainglow/mixin/client/LivingEntityRendererMixin.java b/src/main/java/io/ix0rai/rainglow/mixin/client/LivingEntityRendererMixin.java new file mode 100644 index 0000000..d478a0c --- /dev/null +++ b/src/main/java/io/ix0rai/rainglow/mixin/client/LivingEntityRendererMixin.java @@ -0,0 +1,48 @@ +package io.ix0rai.rainglow.mixin.client; + +import io.ix0rai.rainglow.data.EntityRenderStateTracker; +import io.ix0rai.rainglow.data.RainbowManager; +import io.ix0rai.rainglow.data.RainglowEntity; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.entity.EntityRenderer; +import net.minecraft.client.render.entity.EntityRendererFactory; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.LivingEntity; +import net.minecraft.unmapped.C_ozphnurq; +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.CallbackInfoReturnable; + +import java.util.UUID; + +@Mixin(LivingEntityRenderer.class) +public abstract class LivingEntityRendererMixin> extends EntityRenderer implements FeatureRendererContext { + protected LivingEntityRendererMixin(EntityRendererFactory.Context ctx) { + super(ctx); + } + + @Inject(method = "method_62484", at = @At("RETURN"), cancellable = true) + private void getMixColour(S state, CallbackInfoReturnable cir) { + // Only if this entity uses the State Tracker and has valid UUID + if (state instanceof EntityRenderStateTracker) { + UUID entityUuid = ((EntityRenderStateTracker) state).rainglow$getEntityUuid(); + if (entityUuid != null) { + boolean rainbowState = ((EntityRenderStateTracker) state).rainglow$isRainbow(); + if (rainbowState) { + ClientWorld world = MinecraftClient.getInstance().world; + if (world != null) { + LivingEntity entity = RainbowManager.findEntityByUuid(world, entityUuid); + if (RainglowEntity.get(entity) != null) { + int colour = RainbowManager.getRainbowColor(entityUuid); + cir.setReturnValue(colour); + } + } + } + } + } + } +} diff --git a/src/main/java/io/ix0rai/rainglow/mixin/client/SlimeEntityRendererMixin.java b/src/main/java/io/ix0rai/rainglow/mixin/client/SlimeEntityRendererMixin.java index cf77e8b..d8490cf 100644 --- a/src/main/java/io/ix0rai/rainglow/mixin/client/SlimeEntityRendererMixin.java +++ b/src/main/java/io/ix0rai/rainglow/mixin/client/SlimeEntityRendererMixin.java @@ -23,8 +23,10 @@ public void getTexture(class_10067 state, CallbackInfoReturnable cir if (entityUuid != null) { ClientWorld world = MinecraftClient.getInstance().world; if (world != null) { + boolean rainbowState = ((EntityRenderStateTracker) state).rainglow$isRainbow(); + RainglowEntity type = RainglowEntity.SLIME; - Identifier texture = type.overrideTexture(world, entityUuid); + Identifier texture = type.overrideTexture(world, entityUuid, rainbowState); cir.setReturnValue(texture != null ? texture : type.getDefaultTexture()); } } diff --git a/src/main/java/io/ix0rai/rainglow/mixin/client/SlimeOverlayFeatureRendererMixin.java b/src/main/java/io/ix0rai/rainglow/mixin/client/SlimeOverlayFeatureRendererMixin.java index 48bc6a2..cd01041 100644 --- a/src/main/java/io/ix0rai/rainglow/mixin/client/SlimeOverlayFeatureRendererMixin.java +++ b/src/main/java/io/ix0rai/rainglow/mixin/client/SlimeOverlayFeatureRendererMixin.java @@ -1,6 +1,9 @@ package io.ix0rai.rainglow.mixin.client; +import com.mojang.blaze3d.vertex.VertexConsumer; +import io.ix0rai.rainglow.data.ColorOverlayVertexConsumer; import io.ix0rai.rainglow.data.EntityRenderStateTracker; +import io.ix0rai.rainglow.data.RainbowManager; import io.ix0rai.rainglow.data.RainglowEntity; import net.minecraft.class_10067; import net.minecraft.client.MinecraftClient; @@ -8,8 +11,10 @@ import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.SlimeEntityRenderer; import net.minecraft.client.render.entity.feature.SlimeOverlayFeatureRenderer; +import net.minecraft.client.render.entity.model.SlimeEntityModel; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.LivingEntity; import net.minecraft.util.Identifier; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -44,6 +49,34 @@ public class SlimeOverlayFeatureRendererMixin { return overrideTexture != null ? RenderLayer.getEntityTranslucent(overrideTexture) : RenderLayer.getEntityTranslucent(SlimeEntityRenderer.TEXTURE); } + @Redirect(method = "render*", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/model/SlimeEntityModel;method_60879(Lnet/minecraft/client/util/math/MatrixStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;II)V")) + private void rainglow$renderWithColour(SlimeEntityModel model, MatrixStack matrices, VertexConsumer vertices, int light, int overlay, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, class_10067 state, float f, float g) { + if (state instanceof EntityRenderStateTracker) { + UUID entityUuid = ((EntityRenderStateTracker) state).rainglow$getEntityUuid(); + if (entityUuid != null && ((EntityRenderStateTracker) state).rainglow$isRainbow()) { + ClientWorld world = MinecraftClient.getInstance().world; + if (world != null) { + LivingEntity entity = RainbowManager.findEntityByUuid(world, entityUuid); + if (RainglowEntity.get(entity) != null) { + int rainbowColor = RainbowManager.getRainbowColor(entityUuid); + + float r = ((rainbowColor >> 16) & 0xFF) / 255.0F; + float g1 = ((rainbowColor >> 8) & 0xFF) / 255.0F; + float b = (rainbowColor & 0xFF) / 255.0F; + + VertexConsumer coloredVertices = new ColorOverlayVertexConsumer(vertices, r, g1, b); + + model.method_60879(matrices, coloredVertices, light, overlay); + return; + } + } + } + } + + // Default + model.method_60879(matrices, vertices, light, overlay); + } + @Unique private Identifier getOverrideTexture(class_10067 state) { if (state instanceof EntityRenderStateTracker) { @@ -52,7 +85,9 @@ private Identifier getOverrideTexture(class_10067 state) { ClientWorld world = MinecraftClient.getInstance().world; if (world != null) { try { - return RainglowEntity.SLIME.overrideTexture(world, entityUuid); + boolean rainbowState = ((EntityRenderStateTracker) state).rainglow$isRainbow(); + + return RainglowEntity.SLIME.overrideTexture(world, entityUuid, rainbowState); } catch (Exception e) { // ignore any errors and just let return null for default textures } diff --git a/src/main/resources/rainglow.mixins.json b/src/main/resources/rainglow.mixins.json index 1688a03..7aa9768 100644 --- a/src/main/resources/rainglow.mixins.json +++ b/src/main/resources/rainglow.mixins.json @@ -18,6 +18,7 @@ "client.EntityRenderStateMixin", "client.GlowParticleMixin", "client.GlowSquidEntityRendererMixin", + "client.LivingEntityRendererMixin", "client.SlimeEntityRendererMixin", "client.SlimeOverlayFeatureRendererMixin", "client.SlimeParticleMixin",