minecraft-src/net/minecraft/client/particle/ParticleEngine.java
2025-07-04 03:45:38 +03:00

634 lines
31 KiB
Java

package net.minecraft.client.particle;
import com.google.common.collect.EvictingQueue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.logging.LogUtils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.Util;
import net.minecraft.client.Camera;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.particle.BlockMarker.Provider;
import net.minecraft.client.particle.CampfireSmokeParticle.CosyProvider;
import net.minecraft.client.particle.CampfireSmokeParticle.SignalProvider;
import net.minecraft.client.particle.CritParticle.DamageIndicatorProvider;
import net.minecraft.client.particle.CritParticle.MagicProvider;
import net.minecraft.client.particle.FireworkParticles.FlashProvider;
import net.minecraft.client.particle.FireworkParticles.SparkProvider;
import net.minecraft.client.particle.FlameParticle.SmallFlameProvider;
import net.minecraft.client.particle.FlyStraightTowardsParticle.OminousSpawnProvider;
import net.minecraft.client.particle.FlyTowardsPositionParticle.EnchantProvider;
import net.minecraft.client.particle.FlyTowardsPositionParticle.NautilusProvider;
import net.minecraft.client.particle.FlyTowardsPositionParticle.VaultConnectionProvider;
import net.minecraft.client.particle.GlowParticle.ElectricSparkProvider;
import net.minecraft.client.particle.GlowParticle.GlowSquidProvider;
import net.minecraft.client.particle.GlowParticle.ScrapeProvider;
import net.minecraft.client.particle.GlowParticle.WaxOffProvider;
import net.minecraft.client.particle.GlowParticle.WaxOnProvider;
import net.minecraft.client.particle.GustParticle.SmallProvider;
import net.minecraft.client.particle.HeartParticle.AngryVillagerProvider;
import net.minecraft.client.particle.ParticleProvider.Sprite;
import net.minecraft.client.particle.PlayerCloudParticle.SneezeProvider;
import net.minecraft.client.particle.ReversePortalParticle.ReversePortalProvider;
import net.minecraft.client.particle.SoulParticle.EmissiveProvider;
import net.minecraft.client.particle.SpellParticle.InstantProvider;
import net.minecraft.client.particle.SpellParticle.MobEffectProvider;
import net.minecraft.client.particle.SpellParticle.WitchProvider;
import net.minecraft.client.particle.SquidInkParticle.GlowInkProvider;
import net.minecraft.client.particle.SuspendedParticle.CrimsonSporeProvider;
import net.minecraft.client.particle.SuspendedParticle.SporeBlossomAirProvider;
import net.minecraft.client.particle.SuspendedParticle.UnderwaterProvider;
import net.minecraft.client.particle.SuspendedParticle.WarpedSporeProvider;
import net.minecraft.client.particle.SuspendedTownParticle.ComposterFillProvider;
import net.minecraft.client.particle.SuspendedTownParticle.DolphinSpeedProvider;
import net.minecraft.client.particle.SuspendedTownParticle.EggCrackProvider;
import net.minecraft.client.particle.SuspendedTownParticle.HappyVillagerProvider;
import net.minecraft.client.particle.TerrainParticle.CrumblingProvider;
import net.minecraft.client.particle.TerrainParticle.DustPillarProvider;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.MultiBufferSource.BufferSource;
import net.minecraft.client.renderer.texture.SpriteLoader;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.model.AtlasIds;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.particles.ParticleGroup;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.core.particles.ParticleType;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.FileToIdConverter;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@Environment(EnvType.CLIENT)
public class ParticleEngine implements PreparableReloadListener {
private static final Logger LOGGER = LogUtils.getLogger();
private static final FileToIdConverter PARTICLE_LISTER = FileToIdConverter.json("particles");
private static final int MAX_PARTICLES_PER_LAYER = 16384;
private static final List<ParticleRenderType> RENDER_ORDER = List.of(
ParticleRenderType.TERRAIN_SHEET, ParticleRenderType.PARTICLE_SHEET_OPAQUE, ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT
);
protected ClientLevel level;
private final Map<ParticleRenderType, Queue<Particle>> particles = Maps.<ParticleRenderType, Queue<Particle>>newIdentityHashMap();
private final Queue<TrackingEmitter> trackingEmitters = Queues.<TrackingEmitter>newArrayDeque();
private final RandomSource random = RandomSource.create();
private final Int2ObjectMap<ParticleProvider<?>> providers = new Int2ObjectOpenHashMap<>();
private final Queue<Particle> particlesToAdd = Queues.<Particle>newArrayDeque();
private final Map<ResourceLocation, ParticleEngine.MutableSpriteSet> spriteSets = Maps.<ResourceLocation, ParticleEngine.MutableSpriteSet>newHashMap();
private final TextureAtlas textureAtlas;
private final Object2IntOpenHashMap<ParticleGroup> trackedParticleCounts = new Object2IntOpenHashMap<>();
public ParticleEngine(ClientLevel level, TextureManager textureManager) {
this.textureAtlas = new TextureAtlas(TextureAtlas.LOCATION_PARTICLES);
textureManager.register(this.textureAtlas.location(), this.textureAtlas);
this.level = level;
this.registerProviders();
}
private void registerProviders() {
this.register(ParticleTypes.ANGRY_VILLAGER, AngryVillagerProvider::new);
this.register(ParticleTypes.BLOCK_MARKER, new Provider());
this.register(ParticleTypes.BLOCK, new net.minecraft.client.particle.TerrainParticle.Provider());
this.register(ParticleTypes.BUBBLE, net.minecraft.client.particle.BubbleParticle.Provider::new);
this.register(ParticleTypes.BUBBLE_COLUMN_UP, net.minecraft.client.particle.BubbleColumnUpParticle.Provider::new);
this.register(ParticleTypes.BUBBLE_POP, net.minecraft.client.particle.BubblePopParticle.Provider::new);
this.register(ParticleTypes.CAMPFIRE_COSY_SMOKE, CosyProvider::new);
this.register(ParticleTypes.CAMPFIRE_SIGNAL_SMOKE, SignalProvider::new);
this.register(ParticleTypes.CLOUD, net.minecraft.client.particle.PlayerCloudParticle.Provider::new);
this.register(ParticleTypes.COMPOSTER, ComposterFillProvider::new);
this.register(ParticleTypes.CRIT, net.minecraft.client.particle.CritParticle.Provider::new);
this.register(ParticleTypes.CURRENT_DOWN, net.minecraft.client.particle.WaterCurrentDownParticle.Provider::new);
this.register(ParticleTypes.DAMAGE_INDICATOR, DamageIndicatorProvider::new);
this.register(ParticleTypes.DRAGON_BREATH, net.minecraft.client.particle.DragonBreathParticle.Provider::new);
this.register(ParticleTypes.DOLPHIN, DolphinSpeedProvider::new);
this.register(ParticleTypes.DRIPPING_LAVA, DripParticle::createLavaHangParticle);
this.register(ParticleTypes.FALLING_LAVA, DripParticle::createLavaFallParticle);
this.register(ParticleTypes.LANDING_LAVA, DripParticle::createLavaLandParticle);
this.register(ParticleTypes.DRIPPING_WATER, DripParticle::createWaterHangParticle);
this.register(ParticleTypes.FALLING_WATER, DripParticle::createWaterFallParticle);
this.register(ParticleTypes.DUST, net.minecraft.client.particle.DustParticle.Provider::new);
this.register(ParticleTypes.DUST_COLOR_TRANSITION, net.minecraft.client.particle.DustColorTransitionParticle.Provider::new);
this.register(ParticleTypes.EFFECT, net.minecraft.client.particle.SpellParticle.Provider::new);
this.register(ParticleTypes.ELDER_GUARDIAN, new MobAppearanceParticle.Provider());
this.register(ParticleTypes.ENCHANTED_HIT, MagicProvider::new);
this.register(ParticleTypes.ENCHANT, EnchantProvider::new);
this.register(ParticleTypes.END_ROD, net.minecraft.client.particle.EndRodParticle.Provider::new);
this.register(ParticleTypes.ENTITY_EFFECT, MobEffectProvider::new);
this.register(ParticleTypes.EXPLOSION_EMITTER, new net.minecraft.client.particle.HugeExplosionSeedParticle.Provider());
this.register(ParticleTypes.EXPLOSION, net.minecraft.client.particle.HugeExplosionParticle.Provider::new);
this.register(ParticleTypes.SONIC_BOOM, net.minecraft.client.particle.SonicBoomParticle.Provider::new);
this.register(ParticleTypes.FALLING_DUST, net.minecraft.client.particle.FallingDustParticle.Provider::new);
this.register(ParticleTypes.GUST, net.minecraft.client.particle.GustParticle.Provider::new);
this.register(ParticleTypes.SMALL_GUST, SmallProvider::new);
this.register(ParticleTypes.GUST_EMITTER_LARGE, new net.minecraft.client.particle.GustSeedParticle.Provider(3.0, 7, 0));
this.register(ParticleTypes.GUST_EMITTER_SMALL, new net.minecraft.client.particle.GustSeedParticle.Provider(1.0, 3, 2));
this.register(ParticleTypes.FIREWORK, SparkProvider::new);
this.register(ParticleTypes.FISHING, net.minecraft.client.particle.WakeParticle.Provider::new);
this.register(ParticleTypes.FLAME, net.minecraft.client.particle.FlameParticle.Provider::new);
this.register(ParticleTypes.INFESTED, net.minecraft.client.particle.SpellParticle.Provider::new);
this.register(ParticleTypes.SCULK_SOUL, EmissiveProvider::new);
this.register(ParticleTypes.SCULK_CHARGE, net.minecraft.client.particle.SculkChargeParticle.Provider::new);
this.register(ParticleTypes.SCULK_CHARGE_POP, net.minecraft.client.particle.SculkChargePopParticle.Provider::new);
this.register(ParticleTypes.SOUL, net.minecraft.client.particle.SoulParticle.Provider::new);
this.register(ParticleTypes.SOUL_FIRE_FLAME, net.minecraft.client.particle.FlameParticle.Provider::new);
this.register(ParticleTypes.FLASH, FlashProvider::new);
this.register(ParticleTypes.HAPPY_VILLAGER, HappyVillagerProvider::new);
this.register(ParticleTypes.HEART, net.minecraft.client.particle.HeartParticle.Provider::new);
this.register(ParticleTypes.INSTANT_EFFECT, InstantProvider::new);
this.register(ParticleTypes.ITEM, new BreakingItemParticle.Provider());
this.register(ParticleTypes.ITEM_SLIME, new BreakingItemParticle.SlimeProvider());
this.register(ParticleTypes.ITEM_COBWEB, new BreakingItemParticle.CobwebProvider());
this.register(ParticleTypes.ITEM_SNOWBALL, new BreakingItemParticle.SnowballProvider());
this.register(ParticleTypes.LARGE_SMOKE, net.minecraft.client.particle.LargeSmokeParticle.Provider::new);
this.register(ParticleTypes.LAVA, net.minecraft.client.particle.LavaParticle.Provider::new);
this.register(ParticleTypes.MYCELIUM, net.minecraft.client.particle.SuspendedTownParticle.Provider::new);
this.register(ParticleTypes.NAUTILUS, NautilusProvider::new);
this.register(ParticleTypes.NOTE, net.minecraft.client.particle.NoteParticle.Provider::new);
this.register(ParticleTypes.POOF, net.minecraft.client.particle.ExplodeParticle.Provider::new);
this.register(ParticleTypes.PORTAL, net.minecraft.client.particle.PortalParticle.Provider::new);
this.register(ParticleTypes.RAIN, net.minecraft.client.particle.WaterDropParticle.Provider::new);
this.register(ParticleTypes.SMOKE, net.minecraft.client.particle.SmokeParticle.Provider::new);
this.register(ParticleTypes.WHITE_SMOKE, net.minecraft.client.particle.WhiteSmokeParticle.Provider::new);
this.register(ParticleTypes.SNEEZE, SneezeProvider::new);
this.register(ParticleTypes.SNOWFLAKE, net.minecraft.client.particle.SnowflakeParticle.Provider::new);
this.register(ParticleTypes.SPIT, net.minecraft.client.particle.SpitParticle.Provider::new);
this.register(ParticleTypes.SWEEP_ATTACK, net.minecraft.client.particle.AttackSweepParticle.Provider::new);
this.register(ParticleTypes.TOTEM_OF_UNDYING, net.minecraft.client.particle.TotemParticle.Provider::new);
this.register(ParticleTypes.SQUID_INK, net.minecraft.client.particle.SquidInkParticle.Provider::new);
this.register(ParticleTypes.UNDERWATER, UnderwaterProvider::new);
this.register(ParticleTypes.SPLASH, net.minecraft.client.particle.SplashParticle.Provider::new);
this.register(ParticleTypes.WITCH, WitchProvider::new);
this.register(ParticleTypes.DRIPPING_HONEY, DripParticle::createHoneyHangParticle);
this.register(ParticleTypes.FALLING_HONEY, DripParticle::createHoneyFallParticle);
this.register(ParticleTypes.LANDING_HONEY, DripParticle::createHoneyLandParticle);
this.register(ParticleTypes.FALLING_NECTAR, DripParticle::createNectarFallParticle);
this.register(ParticleTypes.FALLING_SPORE_BLOSSOM, DripParticle::createSporeBlossomFallParticle);
this.register(ParticleTypes.SPORE_BLOSSOM_AIR, SporeBlossomAirProvider::new);
this.register(ParticleTypes.ASH, net.minecraft.client.particle.AshParticle.Provider::new);
this.register(ParticleTypes.CRIMSON_SPORE, CrimsonSporeProvider::new);
this.register(ParticleTypes.WARPED_SPORE, WarpedSporeProvider::new);
this.register(ParticleTypes.DRIPPING_OBSIDIAN_TEAR, DripParticle::createObsidianTearHangParticle);
this.register(ParticleTypes.FALLING_OBSIDIAN_TEAR, DripParticle::createObsidianTearFallParticle);
this.register(ParticleTypes.LANDING_OBSIDIAN_TEAR, DripParticle::createObsidianTearLandParticle);
this.register(ParticleTypes.REVERSE_PORTAL, ReversePortalProvider::new);
this.register(ParticleTypes.WHITE_ASH, net.minecraft.client.particle.WhiteAshParticle.Provider::new);
this.register(ParticleTypes.SMALL_FLAME, SmallFlameProvider::new);
this.register(ParticleTypes.DRIPPING_DRIPSTONE_WATER, DripParticle::createDripstoneWaterHangParticle);
this.register(ParticleTypes.FALLING_DRIPSTONE_WATER, DripParticle::createDripstoneWaterFallParticle);
this.register(ParticleTypes.CHERRY_LEAVES, FallingLeavesParticle.CherryProvider::new);
this.register(ParticleTypes.PALE_OAK_LEAVES, FallingLeavesParticle.PaleOakProvider::new);
this.register(ParticleTypes.TINTED_LEAVES, FallingLeavesParticle.TintedLeavesProvider::new);
this.register(ParticleTypes.DRIPPING_DRIPSTONE_LAVA, DripParticle::createDripstoneLavaHangParticle);
this.register(ParticleTypes.FALLING_DRIPSTONE_LAVA, DripParticle::createDripstoneLavaFallParticle);
this.register(ParticleTypes.VIBRATION, net.minecraft.client.particle.VibrationSignalParticle.Provider::new);
this.register(ParticleTypes.TRAIL, net.minecraft.client.particle.TrailParticle.Provider::new);
this.register(ParticleTypes.GLOW_SQUID_INK, GlowInkProvider::new);
this.register(ParticleTypes.GLOW, GlowSquidProvider::new);
this.register(ParticleTypes.WAX_ON, WaxOnProvider::new);
this.register(ParticleTypes.WAX_OFF, WaxOffProvider::new);
this.register(ParticleTypes.ELECTRIC_SPARK, ElectricSparkProvider::new);
this.register(ParticleTypes.SCRAPE, ScrapeProvider::new);
this.register(ParticleTypes.SHRIEK, net.minecraft.client.particle.ShriekParticle.Provider::new);
this.register(ParticleTypes.EGG_CRACK, EggCrackProvider::new);
this.register(ParticleTypes.DUST_PLUME, net.minecraft.client.particle.DustPlumeParticle.Provider::new);
this.register(ParticleTypes.TRIAL_SPAWNER_DETECTED_PLAYER, net.minecraft.client.particle.TrialSpawnerDetectionParticle.Provider::new);
this.register(ParticleTypes.TRIAL_SPAWNER_DETECTED_PLAYER_OMINOUS, net.minecraft.client.particle.TrialSpawnerDetectionParticle.Provider::new);
this.register(ParticleTypes.VAULT_CONNECTION, VaultConnectionProvider::new);
this.register(ParticleTypes.DUST_PILLAR, new DustPillarProvider());
this.register(ParticleTypes.RAID_OMEN, net.minecraft.client.particle.SpellParticle.Provider::new);
this.register(ParticleTypes.TRIAL_OMEN, net.minecraft.client.particle.SpellParticle.Provider::new);
this.register(ParticleTypes.OMINOUS_SPAWNING, OminousSpawnProvider::new);
this.register(ParticleTypes.BLOCK_CRUMBLE, new CrumblingProvider());
this.register(ParticleTypes.FIREFLY, FireflyParticle.FireflyProvider::new);
}
private <T extends ParticleOptions> void register(ParticleType<T> particleType, ParticleProvider<T> particleFactory) {
this.providers.put(BuiltInRegistries.PARTICLE_TYPE.getId(particleType), particleFactory);
}
private <T extends ParticleOptions> void register(ParticleType<T> particleType, Sprite<T> sprite) {
this.register(particleType, spriteSet -> (particleOptions, clientLevel, d, e, f, g, h, i) -> {
TextureSheetParticle textureSheetParticle = sprite.createParticle(particleOptions, clientLevel, d, e, f, g, h, i);
if (textureSheetParticle != null) {
textureSheetParticle.pickSprite(spriteSet);
}
return textureSheetParticle;
});
}
private <T extends ParticleOptions> void register(ParticleType<T> particleType, ParticleEngine.SpriteParticleRegistration<T> particleMetaFactory) {
ParticleEngine.MutableSpriteSet mutableSpriteSet = new ParticleEngine.MutableSpriteSet();
this.spriteSets.put(BuiltInRegistries.PARTICLE_TYPE.getKey(particleType), mutableSpriteSet);
this.providers.put(BuiltInRegistries.PARTICLE_TYPE.getId(particleType), particleMetaFactory.create(mutableSpriteSet));
}
@Override
public CompletableFuture<Void> reload(
PreparableReloadListener.PreparationBarrier preparationBarrier, ResourceManager resourceManager, Executor executor, Executor executor2
) {
@Environment(EnvType.CLIENT)
record ParticleDefinition(ResourceLocation id, Optional<List<ResourceLocation>> sprites) {
}
CompletableFuture<List<ParticleDefinition>> completableFuture = CompletableFuture.supplyAsync(
() -> PARTICLE_LISTER.listMatchingResources(resourceManager), executor
)
.thenCompose(
map -> {
List<CompletableFuture<ParticleDefinition>> list = new ArrayList(map.size());
map.forEach(
(resourceLocation, resource) -> {
ResourceLocation resourceLocation2 = PARTICLE_LISTER.fileToId(resourceLocation);
list.add(
CompletableFuture.supplyAsync(() -> new ParticleDefinition(resourceLocation2, this.loadParticleDescription(resourceLocation2, resource)), executor)
);
}
);
return Util.sequence(list);
}
);
CompletableFuture<SpriteLoader.Preparations> completableFuture2 = SpriteLoader.create(this.textureAtlas)
.loadAndStitch(resourceManager, AtlasIds.PARTICLES, 0, executor)
.thenCompose(SpriteLoader.Preparations::waitForUpload);
return CompletableFuture.allOf(completableFuture2, completableFuture).thenCompose(preparationBarrier::wait).thenAcceptAsync(void_ -> {
this.clearParticles();
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("upload");
SpriteLoader.Preparations preparations = (SpriteLoader.Preparations)completableFuture2.join();
this.textureAtlas.upload(preparations);
profilerFiller.popPush("bindSpriteSets");
Set<ResourceLocation> set = new HashSet();
TextureAtlasSprite textureAtlasSprite = preparations.missing();
((List)completableFuture.join()).forEach(arg -> {
Optional<List<ResourceLocation>> optional = arg.sprites();
if (!optional.isEmpty()) {
List<TextureAtlasSprite> list = new ArrayList();
for (ResourceLocation resourceLocation : (List)optional.get()) {
TextureAtlasSprite textureAtlasSprite2 = (TextureAtlasSprite)preparations.regions().get(resourceLocation);
if (textureAtlasSprite2 == null) {
set.add(resourceLocation);
list.add(textureAtlasSprite);
} else {
list.add(textureAtlasSprite2);
}
}
if (list.isEmpty()) {
list.add(textureAtlasSprite);
}
((ParticleEngine.MutableSpriteSet)this.spriteSets.get(arg.id())).rebind(list);
}
});
if (!set.isEmpty()) {
LOGGER.warn("Missing particle sprites: {}", set.stream().sorted().map(ResourceLocation::toString).collect(Collectors.joining(",")));
}
profilerFiller.pop();
}, executor2);
}
public void close() {
this.textureAtlas.clearTextureData();
}
private Optional<List<ResourceLocation>> loadParticleDescription(ResourceLocation registryName, Resource resource) {
if (!this.spriteSets.containsKey(registryName)) {
LOGGER.debug("Redundant texture list for particle: {}", registryName);
return Optional.empty();
} else {
try {
Reader reader = resource.openAsReader();
Optional var5;
try {
ParticleDescription particleDescription = ParticleDescription.fromJson(GsonHelper.parse(reader));
var5 = Optional.of(particleDescription.getTextures());
} catch (Throwable var7) {
if (reader != null) {
try {
reader.close();
} catch (Throwable var6) {
var7.addSuppressed(var6);
}
}
throw var7;
}
if (reader != null) {
reader.close();
}
return var5;
} catch (IOException var8) {
throw new IllegalStateException("Failed to load description for particle " + registryName, var8);
}
}
}
public void createTrackingEmitter(Entity entity, ParticleOptions particleData) {
this.trackingEmitters.add(new TrackingEmitter(this.level, entity, particleData));
}
public void createTrackingEmitter(Entity entity, ParticleOptions data, int lifetime) {
this.trackingEmitters.add(new TrackingEmitter(this.level, entity, data, lifetime));
}
@Nullable
public Particle createParticle(ParticleOptions particleData, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) {
Particle particle = this.makeParticle(particleData, x, y, z, xSpeed, ySpeed, zSpeed);
if (particle != null) {
this.add(particle);
return particle;
} else {
return null;
}
}
@Nullable
private <T extends ParticleOptions> Particle makeParticle(T particleData, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) {
ParticleProvider<T> particleProvider = (ParticleProvider<T>)this.providers.get(BuiltInRegistries.PARTICLE_TYPE.getId(particleData.getType()));
return particleProvider == null ? null : particleProvider.createParticle(particleData, this.level, x, y, z, xSpeed, ySpeed, zSpeed);
}
public void add(Particle effect) {
Optional<ParticleGroup> optional = effect.getParticleGroup();
if (optional.isPresent()) {
if (this.hasSpaceInParticleLimit((ParticleGroup)optional.get())) {
this.particlesToAdd.add(effect);
this.updateCount((ParticleGroup)optional.get(), 1);
}
} else {
this.particlesToAdd.add(effect);
}
}
public void tick() {
this.particles.forEach((particleRenderType, queue) -> {
Profiler.get().push(particleRenderType.toString());
this.tickParticleList(queue);
Profiler.get().pop();
});
if (!this.trackingEmitters.isEmpty()) {
List<TrackingEmitter> list = Lists.<TrackingEmitter>newArrayList();
for (TrackingEmitter trackingEmitter : this.trackingEmitters) {
trackingEmitter.tick();
if (!trackingEmitter.isAlive()) {
list.add(trackingEmitter);
}
}
this.trackingEmitters.removeAll(list);
}
Particle particle;
if (!this.particlesToAdd.isEmpty()) {
while ((particle = (Particle)this.particlesToAdd.poll()) != null) {
((Queue)this.particles.computeIfAbsent(particle.getRenderType(), particleRenderType -> EvictingQueue.create(16384))).add(particle);
}
}
}
private void tickParticleList(Collection<Particle> particles) {
if (!particles.isEmpty()) {
Iterator<Particle> iterator = particles.iterator();
while (iterator.hasNext()) {
Particle particle = (Particle)iterator.next();
this.tickParticle(particle);
if (!particle.isAlive()) {
particle.getParticleGroup().ifPresent(particleGroup -> this.updateCount(particleGroup, -1));
iterator.remove();
}
}
}
}
private void updateCount(ParticleGroup group, int count) {
this.trackedParticleCounts.addTo(group, count);
}
private void tickParticle(Particle particle) {
try {
particle.tick();
} catch (Throwable var5) {
CrashReport crashReport = CrashReport.forThrowable(var5, "Ticking Particle");
CrashReportCategory crashReportCategory = crashReport.addCategory("Particle being ticked");
crashReportCategory.setDetail("Particle", particle::toString);
crashReportCategory.setDetail("Particle Type", particle.getRenderType()::toString);
throw new ReportedException(crashReport);
}
}
public void render(Camera camera, float partialTick, BufferSource bufferSource) {
for (ParticleRenderType particleRenderType : RENDER_ORDER) {
Queue<Particle> queue = (Queue<Particle>)this.particles.get(particleRenderType);
if (queue != null && !queue.isEmpty()) {
renderParticleType(camera, partialTick, bufferSource, particleRenderType, queue);
}
}
Queue<Particle> queue2 = (Queue<Particle>)this.particles.get(ParticleRenderType.CUSTOM);
if (queue2 != null && !queue2.isEmpty()) {
renderCustomParticles(camera, partialTick, bufferSource, queue2);
}
bufferSource.endBatch();
}
private static void renderParticleType(Camera camera, float partialTick, BufferSource bufferSource, ParticleRenderType particleType, Queue<Particle> particles) {
VertexConsumer vertexConsumer = bufferSource.getBuffer((RenderType)Objects.requireNonNull(particleType.renderType()));
for (Particle particle : particles) {
try {
particle.render(vertexConsumer, camera, partialTick);
} catch (Throwable var11) {
CrashReport crashReport = CrashReport.forThrowable(var11, "Rendering Particle");
CrashReportCategory crashReportCategory = crashReport.addCategory("Particle being rendered");
crashReportCategory.setDetail("Particle", particle::toString);
crashReportCategory.setDetail("Particle Type", particleType::toString);
throw new ReportedException(crashReport);
}
}
}
private static void renderCustomParticles(Camera camera, float partialTick, BufferSource bufferSource, Queue<Particle> particles) {
PoseStack poseStack = new PoseStack();
for (Particle particle : particles) {
try {
particle.renderCustom(poseStack, bufferSource, camera, partialTick);
} catch (Throwable var10) {
CrashReport crashReport = CrashReport.forThrowable(var10, "Rendering Particle");
CrashReportCategory crashReportCategory = crashReport.addCategory("Particle being rendered");
crashReportCategory.setDetail("Particle", particle::toString);
crashReportCategory.setDetail("Particle Type", "Custom");
throw new ReportedException(crashReport);
}
}
}
public void setLevel(@Nullable ClientLevel level) {
this.level = level;
this.clearParticles();
this.trackingEmitters.clear();
}
public void destroy(BlockPos pos, BlockState state) {
if (!state.isAir() && state.shouldSpawnTerrainParticles()) {
VoxelShape voxelShape = state.getShape(this.level, pos);
double d = 0.25;
voxelShape.forAllBoxes((dx, e, f, g, h, i) -> {
double j = Math.min(1.0, g - dx);
double k = Math.min(1.0, h - e);
double l = Math.min(1.0, i - f);
int m = Math.max(2, Mth.ceil(j / 0.25));
int n = Math.max(2, Mth.ceil(k / 0.25));
int o = Math.max(2, Mth.ceil(l / 0.25));
for (int p = 0; p < m; p++) {
for (int q = 0; q < n; q++) {
for (int r = 0; r < o; r++) {
double s = (p + 0.5) / m;
double t = (q + 0.5) / n;
double u = (r + 0.5) / o;
double v = s * j + dx;
double w = t * k + e;
double x = u * l + f;
this.add(new TerrainParticle(this.level, pos.getX() + v, pos.getY() + w, pos.getZ() + x, s - 0.5, t - 0.5, u - 0.5, state, pos));
}
}
}
});
}
}
/**
* Adds block hit particles for the specified block
*/
public void crack(BlockPos pos, Direction side) {
BlockState blockState = this.level.getBlockState(pos);
if (blockState.getRenderShape() != RenderShape.INVISIBLE && blockState.shouldSpawnTerrainParticles()) {
int i = pos.getX();
int j = pos.getY();
int k = pos.getZ();
float f = 0.1F;
AABB aABB = blockState.getShape(this.level, pos).bounds();
double d = i + this.random.nextDouble() * (aABB.maxX - aABB.minX - 0.2F) + 0.1F + aABB.minX;
double e = j + this.random.nextDouble() * (aABB.maxY - aABB.minY - 0.2F) + 0.1F + aABB.minY;
double g = k + this.random.nextDouble() * (aABB.maxZ - aABB.minZ - 0.2F) + 0.1F + aABB.minZ;
if (side == Direction.DOWN) {
e = j + aABB.minY - 0.1F;
}
if (side == Direction.UP) {
e = j + aABB.maxY + 0.1F;
}
if (side == Direction.NORTH) {
g = k + aABB.minZ - 0.1F;
}
if (side == Direction.SOUTH) {
g = k + aABB.maxZ + 0.1F;
}
if (side == Direction.WEST) {
d = i + aABB.minX - 0.1F;
}
if (side == Direction.EAST) {
d = i + aABB.maxX + 0.1F;
}
this.add(new TerrainParticle(this.level, d, e, g, 0.0, 0.0, 0.0, blockState, pos).setPower(0.2F).scale(0.6F));
}
}
public String countParticles() {
return String.valueOf(this.particles.values().stream().mapToInt(Collection::size).sum());
}
private boolean hasSpaceInParticleLimit(ParticleGroup group) {
return this.trackedParticleCounts.getInt(group) < group.getLimit();
}
private void clearParticles() {
this.particles.clear();
this.particlesToAdd.clear();
this.trackingEmitters.clear();
this.trackedParticleCounts.clear();
}
@Environment(EnvType.CLIENT)
static class MutableSpriteSet implements SpriteSet {
private List<TextureAtlasSprite> sprites;
@Override
public TextureAtlasSprite get(int age, int lifetime) {
return (TextureAtlasSprite)this.sprites.get(age * (this.sprites.size() - 1) / lifetime);
}
@Override
public TextureAtlasSprite get(RandomSource random) {
return (TextureAtlasSprite)this.sprites.get(random.nextInt(this.sprites.size()));
}
public void rebind(List<TextureAtlasSprite> sprites) {
this.sprites = ImmutableList.copyOf(sprites);
}
}
@FunctionalInterface
@Environment(EnvType.CLIENT)
interface SpriteParticleRegistration<T extends ParticleOptions> {
ParticleProvider<T> create(SpriteSet spriteSet);
}
}