diff --git a/src/generated_test/resources/assets/minecraft/particles/cloud.json b/src/generated_test/resources/assets/minecraft/particles/cloud.json new file mode 100644 index 0000000000..271261099b --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/particles/cloud.json @@ -0,0 +1,12 @@ +{ + "textures": [ + "minecraft:generic_7", + "minecraft:generic_6", + "minecraft:generic_5", + "minecraft:generic_4", + "minecraft:generic_3", + "minecraft:generic_2", + "minecraft:generic_1", + "minecraft:generic_0" + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/particles/dripping_lava.json b/src/generated_test/resources/assets/minecraft/particles/dripping_lava.json new file mode 100644 index 0000000000..987e9f30aa --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/particles/dripping_lava.json @@ -0,0 +1,5 @@ +{ + "textures": [ + "minecraft:drip_hang" + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/particles/enchant.json b/src/generated_test/resources/assets/minecraft/particles/enchant.json new file mode 100644 index 0000000000..d2175f03fc --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/particles/enchant.json @@ -0,0 +1,30 @@ +{ + "textures": [ + "minecraft:sga_a", + "minecraft:sga_b", + "minecraft:sga_c", + "minecraft:sga_d", + "minecraft:sga_e", + "minecraft:sga_f", + "minecraft:sga_g", + "minecraft:sga_h", + "minecraft:sga_i", + "minecraft:sga_j", + "minecraft:sga_k", + "minecraft:sga_l", + "minecraft:sga_m", + "minecraft:sga_n", + "minecraft:sga_o", + "minecraft:sga_p", + "minecraft:sga_q", + "minecraft:sga_r", + "minecraft:sga_s", + "minecraft:sga_t", + "minecraft:sga_u", + "minecraft:sga_v", + "minecraft:sga_w", + "minecraft:sga_x", + "minecraft:sga_y", + "minecraft:sga_z" + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/particles/fishing.json b/src/generated_test/resources/assets/minecraft/particles/fishing.json new file mode 100644 index 0000000000..49f427bca6 --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/particles/fishing.json @@ -0,0 +1,8 @@ +{ + "textures": [ + "minecraft:splash_0", + "minecraft:splash_1", + "minecraft:splash_2", + "minecraft:splash_3" + ] +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/common/data/ParticleDescriptionProvider.java b/src/main/java/net/minecraftforge/common/data/ParticleDescriptionProvider.java new file mode 100644 index 0000000000..c54e267688 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/data/ParticleDescriptionProvider.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.common.data; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import net.minecraft.Util; +import net.minecraft.client.particle.ParticleEngine; +import net.minecraft.client.particle.ParticleProvider; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.data.CachedOutput; +import net.minecraft.data.DataProvider; +import net.minecraft.data.PackOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackType; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.ForgeRegistries; +import org.jetbrains.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +/** + * A data provider for {@link net.minecraft.client.particle.ParticleDescription}s. + * + *
To use this provider, extend this class and implement {@link #addDescriptions()}. + * Then, register an instance using {@link net.minecraft.data.DataGenerator#addProvider(boolean, Factory)} + * via the {@link net.minecraftforge.data.event.GatherDataEvent} on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod event bus}. + * + *
A description can be added to a {@link ParticleType} which uses a {@linkplain #sprite(ParticleType, ResourceLocation) sprite} + * or {@linkplain #spriteSet(ParticleType, Iterable) sprite set}. + * + *
{@code
+ * @Override
+ * protected void addDescriptions() {
+ * // Single sprite
+ * this.sprite(ParticleTypes.DRIPPING_LAVA, new ResourceLocation("drip_hang"));
+ *
+ * // Multiple sprites
+ * this.spriteSet(ParticleTypes.CLOUD, new ResourceLocation("generic"), 8, true);
+ * }
+ * }
+ *
+ * A particle description holds a list of textures used when rendering the + * particle to the screen. All registered particle descriptions are stitched + * together into a texture atlas called {@link net.minecraft.client.renderer.texture.TextureAtlas#LOCATION_PARTICLES}. + * A {@link ParticleType} whose particle uses the texture atlas, typically via + * the {@link net.minecraft.client.particle.ParticleRenderType}, can then reference + * the necessary texture during rendering. + * + *
Particles with a particle description must have their particle providers
+ * attached to a {@link ParticleType} as a {@linkplain net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSprite(ParticleType, ParticleProvider.Sprite) sprite}
+ * or {@linkplain net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSpriteSet(ParticleType, ParticleEngine.SpriteParticleRegistration) sprite set}
+ * consumer.
+ *
+ * @see DataProvider
+ * @see net.minecraft.client.particle.ParticleDescription
+ */
+public abstract class ParticleDescriptionProvider implements DataProvider {
+
+ private final PackOutput.PathProvider particlesPath;
+ @VisibleForTesting
+ protected final ExistingFileHelper fileHelper;
+ @VisibleForTesting
+ protected final Map Particle types with this description should be attached to a particle provider
+ * via {@link net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSprite(ParticleType, ParticleProvider.Sprite)}.
+ *
+ * @param type the particle type the textures are applied
+ * for
+ * @param texture the texture to render for the particle type
+ * @throws NullPointerException if the particle type is not registered
+ * @throws IllegalArgumentException if a texture does not have an associated PNG
+ * file, or the particle type has already been
+ * provided
+ */
+ protected void sprite(ParticleType> type, ResourceLocation texture) {
+ this.spriteSet(type, texture);
+ }
+
+ /**
+ * Creates a new particle description that contains multiple textures for the
+ * associated {@link ParticleType}. The textures are generated from a common
+ * name and appended with a number representing the state of the animation.
+ *
+ * Particle types with this description should be attached to a particle provider
+ * via {@link net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSpriteSet(ParticleType, ParticleEngine.SpriteParticleRegistration)}.
+ *
+ * @param type the particle type the textures are applied
+ * for
+ * @param baseName the common name of all the textures
+ * @param numOfTextures the number of textures within the set
+ * @param reverse when {@code true}, the textures will be
+ * listed in descending order
+ * @throws NullPointerException if the particle type is not registered
+ * @throws IllegalArgumentException if a texture does not have an associated PNG
+ * file, or the particle type has already been
+ * provided
+ */
+ protected void spriteSet(ParticleType> type, ResourceLocation baseName, int numOfTextures, boolean reverse) {
+ Preconditions.checkArgument(numOfTextures > 0, "The number of textures to generate must be positive");
+ this.spriteSet(type, () -> new Iterator<>() {
+
+ private int counter = 0;
+
+ @Override
+ public boolean hasNext() {
+ return this.counter < numOfTextures;
+ }
+
+ @Override
+ public ResourceLocation next() {
+ var texture = baseName.withSuffix("_" + (reverse ? numOfTextures - this.counter - 1 : this.counter));
+ this.counter++;
+ return texture;
+ }
+ });
+ }
+
+ /**
+ * Creates a new particle description that contains multiple textures for the
+ * associated {@link ParticleType}. The textures are passed as varargs with
+ * at least one texture present.
+ *
+ * Particle types with this description should be attached to a particle provider
+ * via {@link net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSpriteSet(ParticleType, ParticleEngine.SpriteParticleRegistration)}.
+ *
+ * @param type the particle type the textures are applied
+ * for
+ * @param texture the first texture in the description
+ * @param textures a list of subsequent textures to render for
+ * the particle type
+ * @throws NullPointerException if the particle type is not registered
+ * @throws IllegalArgumentException if a texture does not have an associated PNG
+ * file, or the particle type has already been
+ * provided
+ */
+ protected void spriteSet(ParticleType> type, ResourceLocation texture, ResourceLocation... textures) {
+ this.spriteSet(type, Stream.concat(Stream.of(texture), Arrays.stream(textures))::iterator);
+ }
+
+ /**
+ * Creates a new particle description that contains multiple textures for the
+ * associated {@link ParticleType}. The textures are passed as an iterable.
+ *
+ * Particle types with this description should be attached to a particle provider
+ * via {@link net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSpriteSet(ParticleType, ParticleEngine.SpriteParticleRegistration)}.
+ *
+ * @param type the particle type the textures are applied
+ * for
+ * @param textures a list of textures to render for the
+ * particle type
+ * @throws NullPointerException if the particle type is not registered
+ * @throws IllegalArgumentException if there are no textures provided, a texture
+ * does not have an associated PNG file, or
+ * the particle type has already been provided
+ */
+ protected void spriteSet(ParticleType> type, Iterable{@code
+ * minecraft:generic_0
+ * minecraft:generic_1
+ * minecraft:generic_2
+ * // ...
+ * }
+ *
+ *