feat(onlyfun): add Nourra firework with a skin-face burst
Verification 26.2 / check (pull_request) Canceled after 0s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ChrisM-Pek
2026-09-09 14:54:45 +02:00
co-authored by Cursor
parent da61a3b1ec
commit 77fa9c5d21
22 changed files with 436 additions and 2 deletions
+32 -2
View File
@@ -3916,11 +3916,11 @@ project(":onlyfun") {
"src/main/java/fr/koka99cab/sanctuary26/onlyfun/world/CobbleDungeonStructure.java").text
def dungeonStructureSet = new JsonSlurper().parse(file(
"src/main/resources/data/onlyfun/worldgen/structure_set/cobble_dungeons.json"))
def expectedItems = ["normal_lootbox", "cape_lootbox", "spawn_egg_lootbox", "anomaly_lootbox", "lucky_block"]
def expectedItems = ["normal_lootbox", "cape_lootbox", "spawn_egg_lootbox", "anomaly_lootbox", "lucky_block", "nourra_firework"]
def missingModels = expectedItems.findAll { id ->
!file("src/main/resources/assets/onlyfun/items/${id}.json").exists()
}
def textureIds = ["normal_lootbox", "cape_lootbox", "spawn_egg_lootbox", "anomaly_lootbox"]
def textureIds = ["normal_lootbox", "cape_lootbox", "spawn_egg_lootbox", "anomaly_lootbox", "nourra_firework"]
def invalidTextures = textureIds.findAll { id ->
def texture = file("src/main/resources/assets/onlyfun/textures/item/${id}.png")
if (!texture.isFile()) return true
@@ -4138,6 +4138,36 @@ project(":onlyfun") {
|| !shopCsvText.contains("TRUE,ITEM,iliketomoveit:magic_cloud,1,3072,192,")
|| !javaText.contains("MAGIC_CLOUD, MAGIC_CARPET")
|| !file("src/main/resources/assets/onlyfun/models/block/lucky_block.json").text.contains("onlyfun:block/lucky_block")
|| !javaText.contains("class NourraFireworkItem extends FireworkRocketItem")
|| !javaText.contains("class SkinFaceFireworkExplosions")
|| !javaText.contains("FACE = {")
|| !javaText.contains("HOOD = {")
|| !javaText.contains("HOOD_TOP = {")
|| !javaText.contains("0x4A9DFC")
|| !javaText.contains("TRANSPARENT")
|| !javaText.contains("class SkinFaceFireworkClient")
|| !javaText.contains("DustParticleOptions")
|| !javaText.contains("PIXEL = 1.85D")
|| !javaText.contains("LARGE_PIXEL = 2.7D")
|| !javaText.contains("LARGE_SPARK_SIZE = 5.15F")
|| !javaText.contains("SPARK_LIFETIME_TICKS = 38")
|| !javaText.contains("new Fireworks(3, List.of())")
|| !javaText.contains("LIFETIME_TRIM_TICKS = 5")
|| !javaText.contains("onlyfun\$midFlightLifetime")
|| !javaText.contains("onlyfun\$drawSkinFace")
|| !javaText.contains("output.accept(OnlyFunRegistries.NOURRA_FIREWORK)")
|| !file("src/main/resources/onlyfun.mixins.json").text.contains("FireworkRocketLifetimeMixin")
|| !file("src/main/resources/onlyfun.client.mixins.json").text.contains("FireworkRocketEntityMixin")
|| !file("src/main/resources/onlyfun.client.mixins.json").text.contains("ParticleLifetimeAccessor")
|| !file("src/main/resources/fabric.mod.json").text.contains("onlyfun.mixins.json")
|| !file("src/main/resources/fabric.mod.json").text.contains("onlyfun.client.mixins.json")
|| !file("src/main/resources/data/onlyfun/recipe/nourra_firework.json").text.contains("onlyfun:nourra_firework")
|| ["fr_fr", "en_us", "ru_ru"].any { locale ->
def translations = new JsonSlurper().parse(file(
"src/main/resources/assets/onlyfun/lang/${locale}.json"))
!translations.containsKey("item.onlyfun.nourra_firework")
|| !translations.containsKey("tooltip.onlyfun.nourra_firework")
}
|| !missingModels.isEmpty()
|| !invalidTextureHashes.isEmpty()
|| !invalidTextures.isEmpty()) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

@@ -0,0 +1,100 @@
package fr.koka99cab.sanctuary26.onlyfun.client;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFaceFireworkExplosions;
import fr.koka99cab.sanctuary26.onlyfun.mixin.client.ParticleLifetimeAccessor;
import net.minecraft.client.Minecraft;
import net.minecraft.client.particle.Particle;
import net.minecraft.core.particles.ColorParticleOption;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
import net.minecraft.world.phys.Vec3;
/** Paints the baked skin as a firework burst of coloured sparks, facing the player. */
public final class SkinFaceFireworkClient {
public static final double PIXEL = 1.85D;
public static final double LARGE_PIXEL = 2.7D;
public static final float SPARK_SIZE = 3.6F;
public static final float LARGE_SPARK_SIZE = 5.15F;
public static final int SPARK_LIFETIME_TICKS = 38;
private static final int SUBPIXELS = 2;
private SkinFaceFireworkClient() {
}
public static void explode(FireworkRocketEntity rocket) {
Minecraft minecraft = Minecraft.getInstance();
if (minecraft.level == null || minecraft.particleEngine == null) return;
Vec3 origin = rocket.position();
RandomSource random = rocket.level().getRandom();
minecraft.level.playLocalSound(origin.x, origin.y, origin.z,
SoundEvents.FIREWORK_ROCKET_BLAST, SoundSource.AMBIENT,
20.0F, 0.95F + random.nextFloat() * 0.1F, true);
minecraft.level.addParticle(ColorParticleOption.create(ParticleTypes.FLASH, 0xFFFFFFFF),
origin.x, origin.y, origin.z, 0.0D, 0.0D, 0.0D);
Vec3 look = facing(minecraft, origin);
Vec3 right = look.cross(new Vec3(0.0D, 1.0D, 0.0D));
if (right.lengthSqr() < 1.0E-6D) right = new Vec3(1.0D, 0.0D, 0.0D);
right = right.normalize();
Vec3 up = new Vec3(0.0D, 1.0D, 0.0D);
int[] pixels = SkinFaceFireworkExplosions.portrait();
int width = SkinFaceFireworkExplosions.WIDTH;
int height = SkinFaceFireworkExplosions.portraitHeight();
boolean large = largeBurst(rocket);
double pixel = burstPixel(large);
float sparkSize = burstSparkSize(large);
double midX = (width - 1) / 2.0D;
double midY = (height - 1) / 2.0D;
double step = 1.0D / SUBPIXELS;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int color = visibleSpark(pixels[row * width + col]);
DustParticleOptions dust = new DustParticleOptions(color, sparkSize);
for (int subY = 0; subY < SUBPIXELS; subY++) {
for (int subX = 0; subX < SUBPIXELS; subX++) {
double px = (col - midX) + (subX + 0.5D) * step - 0.5D;
double py = (midY - row) - (subY + 0.5D) * step + 0.5D;
Vec3 offset = right.scale(px * pixel).add(up.scale(py * pixel));
Vec3 pos = origin.add(offset);
Vec3 velocity = offset.scale(0.05D);
Particle spark = minecraft.particleEngine.createParticle(
dust, pos.x, pos.y, pos.z, velocity.x, velocity.y, velocity.z);
if (spark != null) {
((ParticleLifetimeAccessor) spark).onlyfun$setLifetime(SPARK_LIFETIME_TICKS);
}
}
}
}
}
}
public static boolean largeBurst(FireworkRocketEntity rocket) {
return (rocket.getUUID().getLeastSignificantBits() & 1L) != 0L;
}
public static double burstPixel(boolean large) {
return large ? LARGE_PIXEL : PIXEL;
}
public static float burstSparkSize(boolean large) {
return large ? LARGE_SPARK_SIZE : SPARK_SIZE;
}
private static Vec3 facing(Minecraft minecraft, Vec3 origin) {
Vec3 camera = minecraft.gameRenderer.mainCamera().position();
Vec3 horizontal = new Vec3(origin.x - camera.x, 0.0D, origin.z - camera.z);
if (horizontal.lengthSqr() < 1.0E-4D) return new Vec3(0.0D, 0.0D, 1.0D);
return horizontal.normalize();
}
private static int visibleSpark(int rgb) {
int red = rgb >> 16 & 0xFF;
int green = rgb >> 8 & 0xFF;
int blue = rgb & 0xFF;
if (red + green + blue < 24) return 0x2A2A2A;
return rgb;
}
}
@@ -0,0 +1,23 @@
package fr.koka99cab.sanctuary26.onlyfun.item;
import java.util.function.Consumer;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.FireworkRocketItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.TooltipDisplay;
/** Firework rocket that bursts into the baked Nourra skin face. */
public final class NourraFireworkItem extends FireworkRocketItem {
public NourraFireworkItem(Properties properties) {
super(properties);
}
@Override
public void appendHoverText(ItemStack stack, TooltipContext context, TooltipDisplay display,
Consumer<Component> tooltip, TooltipFlag flag) {
tooltip.accept(Component.translatable("tooltip.onlyfun.nourra_firework").withStyle(ChatFormatting.AQUA));
super.appendHoverText(stack, context, display, tooltip, flag);
}
}
@@ -0,0 +1,83 @@
package fr.koka99cab.sanctuary26.onlyfun.item;
import fr.koka99cab.sanctuary26.onlyfun.registry.OnlyFunRegistries;
import java.util.List;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.Fireworks;
/**
* Baked Nourra head (NameMC 6166a439e9354904): inner face, hood overlay, hood top.
* The portrait is drawn client-side with firework sparks.
*/
public final class SkinFaceFireworkExplosions {
public static final int WIDTH = 8;
public static final int HEIGHT = 8;
public static final int TRANSPARENT = -1;
/** Inner head front, top-left origin, row-major RGB. */
public static final int[] FACE = {
0x000000, 0x020202, 0x0A0A0A, 0x111111, 0x111111, 0x0A0A0A, 0x020202, 0x000000,
0x020202, 0x0A0A0A, 0x111111, 0x181818, 0x181818, 0x111111, 0x0A0A0A, 0x020202,
0x0A0A0A, 0x111111, 0x181818, 0x212121, 0x212121, 0x181818, 0x111111, 0x0A0A0A,
0x111111, 0x181818, 0x212121, 0x2B2B2B, 0x2B2B2B, 0x212121, 0x181818, 0x111111,
0x111111, 0x000000, 0x000000, 0xFFF0E1, 0xFFF0E1, 0x000000, 0x000000, 0x111111,
0x0A0A0A, 0xFFFFFF, 0x4A9DFC, 0xFDE8D7, 0xFFF0E1, 0x4A9DFC, 0xFFFFFF, 0x0A0A0A,
0x020202, 0xFFFFFF, 0x4A9DFC, 0xFDE8D7, 0xFDE8D7, 0x4A9DFC, 0xFFFFFF, 0x020202,
0x000000, 0xF0C6A5, 0xF7D5B9, 0xFCE1CA, 0xFCE1CA, 0xF7D5B9, 0xF0C6A5, 0x000000
};
/** Hat/hood overlay on the front; {@link #TRANSPARENT} keeps the inner face visible. */
public static final int[] HOOD = {
0x000000, 0x0D0D0D, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0x0D0D0D, 0x000000,
0x0D0D0D, 0x707070, 0xD8D8D8, 0xE5E5E5, 0xE5E5E5, 0xD8D8D8, 0x707070, 0x0D0D0D,
0xCBCBCB, 0xD8D8D8, 0xFFFFFF, 0x212121, 0x212121, 0x181818, 0xD8D8D8, 0xCBCBCB,
0xD8D8D8, 0x181818, 0x212121, 0x2B2B2B, 0x2B2B2B, 0x212121, 0x181818, 0xD8D8D8,
0x111111, 0x181818, 0x212121, 0x2B2B2B, TRANSPARENT, TRANSPARENT, TRANSPARENT, 0x111111,
0x0A0A0A, 0x111111, 0x181818, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
0x020202, 0x0A0A0A, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT,
TRANSPARENT, 0x020202, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT, TRANSPARENT
};
/** Front edge of the hood top (skin overlay top rows closest to the face). */
public static final int[] HOOD_TOP = {
0xBEBEBE, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0xBEBEBE,
0xB8B8B8, 0xB8B8B8, 0xCBCBCB, 0xD8D8D8, 0xD8D8D8, 0xCBCBCB, 0xB8B8B8, 0xB8B8B8
};
private SkinFaceFireworkExplosions() {
}
/** Vanilla flight 3 minus half a gunpowder step, so the burst sits between 2 and 3. */
public static final int LIFETIME_TRIM_TICKS = 5;
public static Fireworks defaultFireworks() {
return new Fireworks(3, List.of());
}
public static int midFlightLifetime(int vanillaLifetime) {
return Math.max(1, vanillaLifetime - LIFETIME_TRIM_TICKS);
}
public static boolean isSkinFaceRocket(ItemStack stack) {
return !stack.isEmpty() && stack.is(OnlyFunRegistries.NOURRA_FIREWORK);
}
public static int opaqueHoodPixels() {
int count = 0;
for (int color : HOOD) {
if (color != TRANSPARENT) count++;
}
return count;
}
/** Hood top (2 rows) then the 8×8 face with the hood overlay composited. */
public static int[] portrait() {
int[] pixels = new int[WIDTH * (HEIGHT + 2)];
System.arraycopy(HOOD_TOP, 0, pixels, 0, HOOD_TOP.length);
for (int index = 0; index < FACE.length; index++) {
pixels[HOOD_TOP.length + index] = HOOD[index] == TRANSPARENT ? FACE[index] : HOOD[index];
}
return pixels;
}
public static int portraitHeight() {
return HEIGHT + HOOD_TOP.length / WIDTH;
}
}
@@ -0,0 +1,25 @@
package fr.koka99cab.sanctuary26.onlyfun.mixin;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFaceFireworkExplosions;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
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;
@Mixin(FireworkRocketEntity.class)
public abstract class FireworkRocketLifetimeMixin {
@Shadow
private int life;
@Shadow
private int lifetime;
@Inject(method = "tick", at = @At("HEAD"))
private void onlyfun$midFlightLifetime(CallbackInfo callback) {
if (this.life != 0) return;
FireworkRocketEntity rocket = (FireworkRocketEntity) (Object) this;
if (!SkinFaceFireworkExplosions.isSkinFaceRocket(rocket.getItem())) return;
this.lifetime = SkinFaceFireworkExplosions.midFlightLifetime(this.lifetime);
}
}
@@ -0,0 +1,22 @@
package fr.koka99cab.sanctuary26.onlyfun.mixin.client;
import fr.koka99cab.sanctuary26.onlyfun.client.SkinFaceFireworkClient;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFaceFireworkExplosions;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
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;
@Mixin(FireworkRocketEntity.class)
public abstract class FireworkRocketEntityMixin {
@Inject(method = "handleEntityEvent", at = @At("HEAD"), cancellable = true)
private void onlyfun$drawSkinFace(byte id, CallbackInfo callback) {
if (id != 17) return;
FireworkRocketEntity rocket = (FireworkRocketEntity) (Object) this;
if (!rocket.level().isClientSide()) return;
if (!SkinFaceFireworkExplosions.isSkinFaceRocket(rocket.getItem())) return;
SkinFaceFireworkClient.explode(rocket);
callback.cancel();
}
}
@@ -0,0 +1,11 @@
package fr.koka99cab.sanctuary26.onlyfun.mixin.client;
import net.minecraft.client.particle.Particle;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(Particle.class)
public interface ParticleLifetimeAccessor {
@Accessor("lifetime")
void onlyfun$setLifetime(int lifetime);
}
@@ -28,6 +28,7 @@ public final class OnlyFunCreativeTab {
output.accept(OnlyFunRegistries.CAPE_LOOTBOX);
output.accept(OnlyFunRegistries.SPAWN_EGG_LOOTBOX);
output.accept(OnlyFunRegistries.ANOMALY_LOOTBOX);
output.accept(OnlyFunRegistries.NOURRA_FIREWORK);
output.accept(AbjectionRegistries.SHIT_BALL);
output.accept(AbjectionRegistries.SHIT_CUBE_ITEM);
})
@@ -3,6 +3,8 @@ package fr.koka99cab.sanctuary26.onlyfun.registry;
import fr.koka99cab.sanctuary26.onlyfun.OnlyFunMod;
import fr.koka99cab.sanctuary26.onlyfun.block.LuckyBlockBlock;
import fr.koka99cab.sanctuary26.onlyfun.item.LootboxItem;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFaceFireworkExplosions;
import fr.koka99cab.sanctuary26.onlyfun.item.NourraFireworkItem;
import fr.koka99cab.sanctuary26.onlyfun.loot.OnlyFunGenerator;
import fr.koka99cab.sanctuary26.onlyfun.loot.OnlyFunSource;
import java.util.function.Function;
@@ -32,6 +34,9 @@ public final class OnlyFunRegistries {
public static final Item CAPE_LOOTBOX = registerLootbox("cape_lootbox", OnlyFunSource.CAPE_LOOTBOX);
public static final Item SPAWN_EGG_LOOTBOX = registerLootbox("spawn_egg_lootbox", OnlyFunSource.SPAWN_EGG_LOOTBOX);
public static final Item ANOMALY_LOOTBOX = registerLootbox("anomaly_lootbox", OnlyFunSource.ANOMALY_LOOTBOX);
public static final Item NOURRA_FIREWORK = registerItem("nourra_firework",
properties -> new NourraFireworkItem(properties.stacksTo(64)
.component(DataComponents.FIREWORKS, SkinFaceFireworkExplosions.defaultFireworks())));
private static boolean initialized;
private OnlyFunRegistries() {
@@ -61,6 +66,11 @@ public final class OnlyFunRegistries {
OnlyFunMod.LOGGER.debug("[{}] Item and block registries initialized.", OnlyFunMod.DISPLAY_NAME);
}
private static Item registerItem(String path, Function<Item.Properties, Item> factory) {
ResourceKey<Item> key = ResourceKey.create(Registries.ITEM, OnlyFunMod.id(path));
return Registry.register(BuiltInRegistries.ITEM, key, factory.apply(new Item.Properties().setId(key)));
}
private static Item registerLootbox(String path, OnlyFunSource source) {
ResourceKey<Item> key = ResourceKey.create(Registries.ITEM, OnlyFunMod.id(path));
return Registry.register(BuiltInRegistries.ITEM, key,
@@ -0,0 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "onlyfun:item/nourra_firework"
}
}
@@ -6,6 +6,8 @@
"item.onlyfun.cape_lootbox": "Cape Lootbox",
"item.onlyfun.spawn_egg_lootbox": "Spawn Egg Lootbox",
"item.onlyfun.anomaly_lootbox": "Anomaly Lootbox",
"item.onlyfun.nourra_firework": "Nourra Firework",
"tooltip.onlyfun.nourra_firework": "The burst paints the skin face, at normal or larger size.",
"item.onlyfun.quality_gear": "Quality Gear",
"item.onlyfun.impossible_tool": "Impossible Tool",
"item.onlyfun.impossible_book": "Impossible Book",
@@ -6,6 +6,8 @@
"item.onlyfun.cape_lootbox": "Lootbox de capes",
"item.onlyfun.spawn_egg_lootbox": "Lootbox d'œufs",
"item.onlyfun.anomaly_lootbox": "Lootbox anomalie",
"item.onlyfun.nourra_firework": "Feu d'artifice Nourra",
"tooltip.onlyfun.nourra_firework": "L'explosion dessine le visage, en taille normale ou plus grande.",
"item.onlyfun.quality_gear": "Équipement de qualité",
"item.onlyfun.impossible_tool": "Outil impossible",
"item.onlyfun.impossible_book": "Livre impossible",
@@ -6,6 +6,8 @@
"item.onlyfun.cape_lootbox": "Лутбокс плащей",
"item.onlyfun.spawn_egg_lootbox": "Лутбокс яиц призыва",
"item.onlyfun.anomaly_lootbox": "Лутбокс-аномалия",
"item.onlyfun.nourra_firework": "Фейерверк Нурры",
"tooltip.onlyfun.nourra_firework": "Взрыв рисует лицо скина обычного или увеличенного размера.",
"item.onlyfun.quality_gear": "Качественное снаряжение",
"item.onlyfun.impossible_tool": "Невозможный инструмент",
"item.onlyfun.impossible_book": "Невозможная книга",
@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "onlyfun:item/nourra_firework"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

@@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
"minecraft:firework_rocket",
"minecraft:light_blue_dye",
"minecraft:paper"
],
"result": {
"count": 1,
"id": "onlyfun:nourra_firework"
}
}
@@ -21,6 +21,13 @@
"fr.koka99cab.sanctuary26.onlyfun.client.OnlyFunClient"
]
},
"mixins": [
"onlyfun.mixins.json",
{
"config": "onlyfun.client.mixins.json",
"environment": "client"
}
],
"depends": {
"fabricloader": ">=0.19.3",
"minecraft": "~26.2",
@@ -0,0 +1,12 @@
{
"required": true,
"package": "fr.koka99cab.sanctuary26.onlyfun.mixin.client",
"compatibilityLevel": "JAVA_25",
"client": [
"FireworkRocketEntityMixin",
"ParticleLifetimeAccessor"
],
"injectors": {
"defaultRequire": 1
}
}
@@ -0,0 +1,11 @@
{
"required": true,
"package": "fr.koka99cab.sanctuary26.onlyfun.mixin",
"compatibilityLevel": "JAVA_25",
"mixins": [
"FireworkRocketLifetimeMixin"
],
"injectors": {
"defaultRequire": 1
}
}
@@ -0,0 +1,66 @@
package fr.koka99cab.sanctuary26.onlyfun.item;
import fr.koka99cab.sanctuary26.onlyfun.client.SkinFaceFireworkClient;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.world.item.component.Fireworks;
/** Executable check for the baked Nourra face and hood firework contract. */
public final class SkinFaceFireworkSmoke {
private SkinFaceFireworkSmoke() {
}
public static void main(String[] args) {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
verify();
}
public static void verify() {
require(SkinFaceFireworkExplosions.WIDTH == 8 && SkinFaceFireworkExplosions.HEIGHT == 8,
"Face grid is no longer 8×8");
require(SkinFaceFireworkExplosions.FACE.length == 64, "Face palette length drifted");
require(SkinFaceFireworkExplosions.HOOD.length == 64, "Hood overlay length drifted");
require(SkinFaceFireworkExplosions.HOOD_TOP.length == 16, "Hood top strip length drifted");
require(SkinFaceFireworkExplosions.FACE[5 * 8 + 2] == 0x4A9DFC
&& SkinFaceFireworkExplosions.FACE[5 * 8 + 5] == 0x4A9DFC,
"Blue eyes are missing from the inner face");
require(SkinFaceFireworkExplosions.FACE[4 * 8 + 3] == 0xFFF0E1,
"Skin tone is missing from the baked face");
require(SkinFaceFireworkExplosions.HOOD[2] == 0xCBCBCB,
"Hood overlay is missing from the top of the face");
require(SkinFaceFireworkExplosions.HOOD[5 * 8 + 2] == 0x181818,
"Hood no longer covers the left eye");
require(SkinFaceFireworkExplosions.HOOD[5 * 8 + 5] == SkinFaceFireworkExplosions.TRANSPARENT,
"Hood opening no longer leaves the right eye visible");
require(SkinFaceFireworkExplosions.opaqueHoodPixels() == 43,
"Opaque hood pixel count drifted");
Fireworks fireworks = SkinFaceFireworkExplosions.defaultFireworks();
require(fireworks.flightDuration() == 3 && fireworks.explosions().isEmpty(),
"Default firework burst contract changed");
require(SkinFaceFireworkExplosions.LIFETIME_TRIM_TICKS == 5
&& SkinFaceFireworkExplosions.midFlightLifetime(45) == 40,
"Mid-flight lifetime trim drifted");
int[] portrait = SkinFaceFireworkExplosions.portrait();
require(portrait.length == 80 && SkinFaceFireworkExplosions.portraitHeight() == 10,
"Portrait grid drifted");
require(portrait[16 + 5 * 8 + 5] == 0x4A9DFC,
"Right eye is missing from the composited portrait");
require(portrait[2] == 0xD8D8D8,
"Hood top is missing from the portrait");
require(SkinFaceFireworkClient.PIXEL == 1.85D, "Portrait scale drifted");
require(SkinFaceFireworkClient.LARGE_PIXEL == 2.7D, "Large portrait scale drifted");
require(SkinFaceFireworkClient.burstPixel(false) == SkinFaceFireworkClient.PIXEL
&& SkinFaceFireworkClient.burstSparkSize(false) == SkinFaceFireworkClient.SPARK_SIZE,
"Normal burst scale drifted");
require(SkinFaceFireworkClient.burstPixel(true) == SkinFaceFireworkClient.LARGE_PIXEL
&& SkinFaceFireworkClient.burstSparkSize(true) == SkinFaceFireworkClient.LARGE_SPARK_SIZE,
"Large burst scale drifted");
require(SkinFaceFireworkClient.SPARK_LIFETIME_TICKS == 38, "Spark lifetime drifted");
System.out.println("Only Fun Nourra firework: 8×8 face with hood overlay ready.");
}
private static void require(boolean condition, String message) {
if (!condition) throw new IllegalStateException(message);
}
}
@@ -1,5 +1,6 @@
package fr.koka99cab.sanctuary26.onlyfun.loot;
import fr.koka99cab.sanctuary26.onlyfun.item.SkinFaceFireworkSmoke;
import fr.koka99cab.sanctuary26.onlyfun.shop.OnlyFunShopCatalogSmoke;
import fr.koka99cab.sanctuary26.onlyfun.world.CobbleDungeonPlacementSmoke;
import java.util.EnumMap;
@@ -18,6 +19,7 @@ public final class OnlyFunGeneratorSmoke {
Bootstrap.bootStrap();
OnlyFunShopCatalogSmoke.verify();
CobbleDungeonPlacementSmoke.verify();
SkinFaceFireworkSmoke.verify();
OnlyFunGenerator.initialize();
require(OnlyFunGenerator.ANOMALY_DENOMINATOR == 256, "Anomaly odds changed from 1/256");
require(OnlyFunGenerator.PITY_START == 24, "Pity threshold changed");