From 2077a8da868085aa0f78bf6e164496a21d89d49d Mon Sep 17 00:00:00 2001 From: Algent Date: Mon, 14 Sep 2026 10:51:15 +0200 Subject: [PATCH] Fix missing colour tint on block particles (#57) --- .../lib/render/EntityDigIconFX.java | 87 +++++++++++++++---- .../lib/render/EntityDigIconFXTest.java | 20 +++++ 2 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 src/test/java/codechicken/lib/render/EntityDigIconFXTest.java diff --git a/src/main/java/codechicken/lib/render/EntityDigIconFX.java b/src/main/java/codechicken/lib/render/EntityDigIconFX.java index fd2e631..8e4afa9 100644 --- a/src/main/java/codechicken/lib/render/EntityDigIconFX.java +++ b/src/main/java/codechicken/lib/render/EntityDigIconFX.java @@ -1,5 +1,7 @@ package codechicken.lib.render; +import java.util.Arrays; + import net.minecraft.client.particle.EffectRenderer; import net.minecraft.client.particle.EntityFX; import net.minecraft.client.renderer.Tessellator; @@ -11,6 +13,9 @@ import codechicken.lib.vec.Vector3; public class EntityDigIconFX extends EntityFX { + /** Tint that leaves the particle colour untouched, for materials that don't colour their texture. */ + public static final int NO_TINT = 0xFFFFFF; + public EntityDigIconFX(World world, double x, double y, double z, double dx, double dy, double dz, IIcon icon) { super(world, x, y, z, dx, dy, dz); particleIcon = icon; @@ -19,6 +24,18 @@ public class EntityDigIconFX extends EntityFX { particleScale /= 2.0F; } + /** + * Multiplies the particle colour by an 0xRRGGBB tint, the equivalent of vanilla + * EntityDiggingFX.applyColourMultiplier. Materials that draw a greyscale texture and colour it at render time + * (leaves, grass tops) need this, or their particles come out flat grey. + */ + public EntityDigIconFX applyColourMultiplier(int colour) { + particleRed *= (colour >> 16 & 255) / 255F; + particleGreen *= (colour >> 8 & 255) / 255F; + particleBlue *= (colour & 255) / 255F; + return this; + } + @Override public int getFXLayer() { return 1; @@ -90,8 +107,24 @@ public class EntityDigIconFX extends EntityFX { f9); } + /** + * @deprecated the particle is never tinted, so a material that colours a greyscale texture at render time (leaves, + * grass tops) breaks into grey particles. Use + * {@link #addBlockHitEffects(World, Cuboid6, int, IIcon, int, EffectRenderer)}, passing + * {@link #NO_TINT} for materials whose texture is already coloured. + */ + @Deprecated public static void addBlockHitEffects(World world, Cuboid6 bounds, int side, IIcon icon, EffectRenderer effectRenderer) { + addBlockHitEffects(world, bounds, side, icon, NO_TINT, effectRenderer); + } + + /** + * Spawns a hit particle on side of bounds, tinted by colour (0xRRGGBB, {@link #NO_TINT} for none). The caller + * resolves the tint for the side it hit, so per-face materials like grass can tint only the faces that need it. + */ + public static void addBlockHitEffects(World world, Cuboid6 bounds, int side, IIcon icon, int colour, + EffectRenderer effectRenderer) { float border = 0.1F; Vector3 diff = bounds.max.copy().subtract(bounds.min).add(-2 * border); diff.x *= world.rand.nextDouble(); @@ -106,13 +139,34 @@ public class EntityDigIconFX extends EntityFX { if (side == 4) diff.x = bounds.min.x - border; if (side == 5) diff.x = bounds.max.x + border; - effectRenderer.addEffect( - new EntityDigIconFX(world, pos.x, pos.y, pos.z, 0, 0, 0, icon).multiplyVelocity(0.2F) - .multipleParticleScaleBy(0.6F)); + EntityDigIconFX fx = new EntityDigIconFX(world, pos.x, pos.y, pos.z, 0, 0, 0, icon); + effectRenderer.addEffect(fx.applyColourMultiplier(colour).multiplyVelocity(0.2F).multipleParticleScaleBy(0.6F)); } + /** + * @deprecated the particles are never tinted, so a material that colours a greyscale texture at render time + * (leaves, grass tops) breaks into grey particles. Use + * {@link #addBlockDestroyEffects(World, Cuboid6, IIcon[], int[], EffectRenderer)}, passing + * {@link #NO_TINT} for the faces whose texture is already coloured. + */ + @Deprecated public static void addBlockDestroyEffects(World world, Cuboid6 bounds, IIcon[] icons, EffectRenderer effectRenderer) { + int[] colours = new int[icons.length]; + Arrays.fill(colours, NO_TINT); + addBlockDestroyEffects(world, bounds, icons, colours, effectRenderer); + } + + /** + * Spawns break particles filling bounds, each drawn from a random face. colours holds the 0xRRGGBB tint for the + * matching entry of icons ({@link #NO_TINT} for none) and must be the same length, so a particle is tinted by the + * face it was taken from. + */ + public static void addBlockDestroyEffects(World world, Cuboid6 bounds, IIcon[] icons, int[] colours, + EffectRenderer effectRenderer) { + if (icons.length != colours.length) + throw new IllegalArgumentException("icons and colours must have the same length"); + Vector3 diff = bounds.max.copy().subtract(bounds.min); Vector3 center = bounds.min.copy().add(bounds.max).multiply(0.5); Vector3 density = diff.copy().multiply(4); @@ -121,19 +175,20 @@ public class EntityDigIconFX extends EntityFX { density.z = Math.ceil(density.z); for (int i = 0; i < density.x; ++i) for (int j = 0; j < density.y; ++j) for (int k = 0; k < density.z; ++k) { - double x = bounds.min.x + (i + 0.5) * diff.x / density.x; - double y = bounds.min.y + (j + 0.5) * diff.y / density.y; - double z = bounds.min.z + (k + 0.5) * diff.z / density.z; - effectRenderer.addEffect( - new EntityDigIconFX( - world, - x, - y, - z, - x - center.x, - y - center.y, - z - center.z, - icons[world.rand.nextInt(icons.length)])); + double px = bounds.min.x + (i + 0.5) * diff.x / density.x; + double py = bounds.min.y + (j + 0.5) * diff.y / density.y; + double pz = bounds.min.z + (k + 0.5) * diff.z / density.z; + int iconSide = world.rand.nextInt(icons.length); + EntityDigIconFX fx = new EntityDigIconFX( + world, + px, + py, + pz, + px - center.x, + py - center.y, + pz - center.z, + icons[iconSide]); + effectRenderer.addEffect(fx.applyColourMultiplier(colours[iconSide])); } } } diff --git a/src/test/java/codechicken/lib/render/EntityDigIconFXTest.java b/src/test/java/codechicken/lib/render/EntityDigIconFXTest.java new file mode 100644 index 0000000..13441bf --- /dev/null +++ b/src/test/java/codechicken/lib/render/EntityDigIconFXTest.java @@ -0,0 +1,20 @@ +package codechicken.lib.render; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import net.minecraft.util.IIcon; + +import org.junit.jupiter.api.Test; + +public class EntityDigIconFXTest { + + @Test + public void rejectsMismatchedIconAndColourCountsBeforeSpawning() { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> EntityDigIconFX.addBlockDestroyEffects(null, null, new IIcon[1], new int[0], null)); + + assertEquals("icons and colours must have the same length", exception.getMessage()); + } +}