Skip to content

Instantly share code, notes, and snippets.

@Ensamisten
Last active April 15, 2026 10:39
Show Gist options
  • Select an option

  • Save Ensamisten/862e8253abcc2b93f7990e18f1cb52d4 to your computer and use it in GitHub Desktop.

Select an option

Save Ensamisten/862e8253abcc2b93f7990e18f1cb52d4 to your computer and use it in GitHub Desktop.
package io.github.ensamisten.util;
import net.minecraft.world.item.component.FireworkExplosion;
public class CustomFireworkShapes {
public static FireworkExplosion.Shape RAINBOW;
}
{
"item.minecraft.firework_star.shape.rainbow": "Rainbow"
}
package io.github.ensamisten.mixin;
import com.mojang.serialization.Codec;
import io.github.ensamisten.util.CustomFireworkShapes;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.util.ByIdMap;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.item.component.FireworkExplosion;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Arrays;
import java.util.function.IntFunction;
@Mixin(FireworkExplosion.Shape.class)
public abstract class FireworkExplosionMixin {
@Mutable @Final @Shadow
private static FireworkExplosion.Shape[] $VALUES;
// Rebuild these after appending
@Mutable @Final @Shadow
private static IntFunction<FireworkExplosion.Shape> BY_ID;
@Mutable @Final @Shadow
public static Codec<FireworkExplosion.Shape> CODEC;
@Mutable @Final @Shadow
public static StreamCodec<ByteBuf, FireworkExplosion.Shape> STREAM_CODEC;
@Invoker("<init>")
private static FireworkExplosion.Shape invokeInit(
String internalName, int ordinal, int id, String name) {
throw new AssertionError();
}
@Inject(method = "<clinit>", at = @At("TAIL"))
private static void addCustomShapes(CallbackInfo ci) {
FireworkExplosion.Shape rainbow = invokeInit(
"RAINBOW",
$VALUES.length,
5,
"enderman_face"
);
FireworkExplosion.Shape[] newValues = Arrays.copyOf($VALUES, $VALUES.length + 1);
newValues[$VALUES.length] = rainbow;
$VALUES = newValues;
// Rebuild BY_ID so network deserialization works
BY_ID = ByIdMap.continuous(
FireworkExplosion.Shape::getId,
FireworkExplosion.Shape.values(),
ByIdMap.OutOfBoundsStrategy.ZERO
);
// Rebuild CODEC so string deserialization works — fixes "Unknown element name"
CODEC = StringRepresentable.fromValues(FireworkExplosion.Shape::values);
CustomFireworkShapes.RAINBOW = rainbow;
STREAM_CODEC = ByteBufCodecs.idMapper(BY_ID, FireworkExplosion.Shape::getId);
System.out.println("[YourMod] Registered shape, total shapes: "
+ FireworkExplosion.Shape.values().length); // should print 6
}
}
package io.github.ensamisten.client.mixin;
import io.github.ensamisten.util.CustomFireworkShapes;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.client.particle.FireworkParticles;
import net.minecraft.world.item.component.FireworkExplosion;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
@Mixin(FireworkParticles.Starter.class)
public abstract class FireworkParticlesMixin {
@Shadow private int life;
@Final
@Shadow private List<FireworkExplosion> explosions;
@Shadow
protected abstract void createParticle(
double x, double y, double z,
double xa, double ya, double za,
IntList rgbColors, IntList fadeColors,
boolean trail, boolean twinkle
);
@Inject(method = "tick", at = @At("HEAD"))
private void injectRainbow(CallbackInfo ci) {
if (this.life % 2 != 0) return;
int eIndex = this.life / 2;
if (eIndex >= this.explosions.size()) return;
FireworkExplosion explosion = this.explosions.get(eIndex);
if (explosion.shape().getId() != 5) return;
// Cast this to the accessor to get x/y/z from Particle superclass
ParticleAccessor self = (ParticleAccessor)(Object) this;
double px = self.getX();
double py = self.getY();
double pz = self.getZ();
boolean trail = explosion.hasTrail();
boolean twinkle = explosion.hasTwinkle();
IntList noFade = IntList.of();
int[] colors = { 0xFF0000, 0xFF7F00, 0xFFFF00, 0x00FF00, 0x0000FF, 0x4B0082, 0x9400D3 };
double[] radii = { 0.90, 0.78, 0.66, 0.54, 0.42, 0.30, 0.18 };
for (int b = 0; b < 7; b++) {
IntList color = IntList.of(colors[b]);
double r = radii[b];
int steps = 32;
for (int i = 0; i <= steps; i++) {
double angle = Math.PI * i / steps;
double xa = -Math.cos(angle) * r;
double ya = Math.sin(angle) * r;
double za = 0.0;
createParticle(px, py, pz, xa, ya, za, color, noFade, trail, twinkle);
}
}
}
}
package io.github.ensamisten.client.mixin;
import net.minecraft.client.particle.Particle;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(Particle.class)
public interface ParticleAccessor {
@Accessor("x") double getX();
@Accessor("y") double getY();
@Accessor("z") double getZ();
}
package io.github.ensamisten.client.particle;
public class Rainbow {
// Arc from left to right across the top — semicircle points
// createParticleShape will lerp between these points
public static final double[][] RAINBOW_ARC = {
{-1.0, 0.0}, // far left
{-0.87, 0.5}, // left curve
{-0.5, 0.87}, // upper left
{ 0.0, 1.0}, // top center
{ 0.5, 0.87}, // upper right
{ 0.87, 0.5}, // right curve
{ 1.0, 0.0}, // far right
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment