634 lines
31 KiB
Java
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);
|
|
}
|
|
}
|