Adds code for spawning customizable particle and trail effects
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				EpicKnarvik97/KnarLib/pipeline/head This commit looks good
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	EpicKnarvik97/KnarLib/pipeline/head This commit looks good
				
			This commit is contained in:
		
							
								
								
									
										167
									
								
								src/main/java/net/knarcraft/knarlib/particle/ParticleConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								src/main/java/net/knarcraft/knarlib/particle/ParticleConfig.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | |||||||
|  | package net.knarcraft.knarlib.particle; | ||||||
|  |  | ||||||
|  | import org.bukkit.Particle; | ||||||
|  | import org.bukkit.configuration.ConfigurationSection; | ||||||
|  | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A configuration describing a particle | ||||||
|  |  */ | ||||||
|  | @SuppressWarnings("unused") | ||||||
|  | public class ParticleConfig { | ||||||
|  |  | ||||||
|  |     private final ParticleMode particleMode; | ||||||
|  |     private final Particle particleType; | ||||||
|  |     private final int particleAmount; | ||||||
|  |     private final double particleDensity; | ||||||
|  |     private final double heightOffset; | ||||||
|  |     private final double offsetX; | ||||||
|  |     private final double offsetY; | ||||||
|  |     private final double offsetZ; | ||||||
|  |     private final double extra; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Instantiates a new particle config | ||||||
|  |      * | ||||||
|  |      * @param particlesSection <p>The configuration section containing the particle's settings</p> | ||||||
|  |      */ | ||||||
|  |     public ParticleConfig(@NotNull ConfigurationSection particlesSection) { | ||||||
|  |         @NotNull Particle particleType; | ||||||
|  |         try { | ||||||
|  |             particleType = Particle.valueOf(particlesSection.getString("type")); | ||||||
|  |         } catch (IllegalArgumentException | NullPointerException exception) { | ||||||
|  |             particleType = Particle.ASH; | ||||||
|  |         } | ||||||
|  |         this.particleType = particleType; | ||||||
|  |         this.particleAmount = particlesSection.getInt("amount", 30); | ||||||
|  |         this.offsetX = particlesSection.getDouble("offsetX", 0.5); | ||||||
|  |         this.offsetY = particlesSection.getDouble("offsetY", 1); | ||||||
|  |         this.offsetZ = particlesSection.getDouble("offsetZ", 0.5); | ||||||
|  |         this.heightOffset = particlesSection.getDouble("heightOffset", 0.5); | ||||||
|  |         this.extra = particlesSection.getDouble("extra", 0); | ||||||
|  |         ParticleMode particleMode; | ||||||
|  |         try { | ||||||
|  |             particleMode = ParticleMode.valueOf(particlesSection.getString("mode")); | ||||||
|  |         } catch (IllegalArgumentException | NullPointerException exception) { | ||||||
|  |             particleMode = ParticleMode.SINGLE; | ||||||
|  |         } | ||||||
|  |         this.particleMode = particleMode; | ||||||
|  |  | ||||||
|  |         // Make sure particle density is between 1 (inclusive) and 0 (exclusive) | ||||||
|  |         double particleDensity = particlesSection.getDouble("particleDensity", 0.1); | ||||||
|  |         if (particleDensity <= 0) { | ||||||
|  |             particleDensity = 0.1; | ||||||
|  |         } else if (particleDensity > 360) { | ||||||
|  |             particleDensity = 360; | ||||||
|  |         } | ||||||
|  |         this.particleDensity = particleDensity; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Instantiates a new particle config | ||||||
|  |      * | ||||||
|  |      * @param particleMode    <p>The mode to use when spawning the particle</p> | ||||||
|  |      * @param particleType    <p>The type of particle to spawn</p> | ||||||
|  |      * @param particleAmount  <p>The amount of particles to spawn at once</p> | ||||||
|  |      * @param particleDensity <p>The density of the particles, if spawning a shape</p> | ||||||
|  |      * @param heightOffset    <p>The offset above the block to spawn the particle</p> | ||||||
|  |      * @param offsetX         <p>The x-offset/spread of the spawned particles</p> | ||||||
|  |      * @param offsetY         <p>The y-offset/spread of the spawned particles</p> | ||||||
|  |      * @param offsetZ         <p>The z-offset/spread of the spawned particles</p> | ||||||
|  |      * @param extra           <p>Extra data for the particle. Usage depends on the particle type.</p> | ||||||
|  |      */ | ||||||
|  |     public ParticleConfig(ParticleMode particleMode, Particle particleType, int particleAmount, double particleDensity, | ||||||
|  |                           double heightOffset, double offsetX, double offsetY, double offsetZ, double extra) { | ||||||
|  |         this.particleMode = particleMode; | ||||||
|  |         this.particleType = particleType; | ||||||
|  |         this.particleAmount = particleAmount; | ||||||
|  |         this.particleDensity = particleDensity; | ||||||
|  |         this.heightOffset = heightOffset; | ||||||
|  |         this.offsetX = offsetX; | ||||||
|  |         this.offsetY = offsetY; | ||||||
|  |         this.offsetZ = offsetZ; | ||||||
|  |         this.extra = extra; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The mode to use when drawing/spawning the particle(s) | ||||||
|  |      * | ||||||
|  |      * @return <p>The particle mode</p> | ||||||
|  |      */ | ||||||
|  |     public ParticleMode getParticleMode() { | ||||||
|  |         return particleMode; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The type of particle to spawn | ||||||
|  |      * | ||||||
|  |      * @return <p>The particle type</p> | ||||||
|  |      */ | ||||||
|  |     public Particle getParticleType() { | ||||||
|  |         return particleType; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The amount of particles to spawn | ||||||
|  |      * | ||||||
|  |      * @return <p>The amount of particles</p> | ||||||
|  |      */ | ||||||
|  |     public int getParticleAmount() { | ||||||
|  |         return particleAmount; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The density of particles to use in shapes closer to 0 causes larger density | ||||||
|  |      * | ||||||
|  |      * @return <p>The particle density</p> | ||||||
|  |      */ | ||||||
|  |     public double getParticleDensity() { | ||||||
|  |         return particleDensity; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The number of blocks above the block the particle(s) should spawn | ||||||
|  |      * | ||||||
|  |      * @return <p>The y-offset</p> | ||||||
|  |      */ | ||||||
|  |     public double getHeightOffset() { | ||||||
|  |         return heightOffset; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The offset/spread of particles in the x-direction | ||||||
|  |      * | ||||||
|  |      * @return <p>The x-offset</p> | ||||||
|  |      */ | ||||||
|  |     public double getOffsetX() { | ||||||
|  |         return offsetX; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The offset/spread of particles in the y-direction | ||||||
|  |      * | ||||||
|  |      * @return <p>The y-offset</p> | ||||||
|  |      */ | ||||||
|  |     public double getOffsetY() { | ||||||
|  |         return offsetY; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The offset/spread of particles in the z-direction | ||||||
|  |      * | ||||||
|  |      * @return <p>The z-offset</p> | ||||||
|  |      */ | ||||||
|  |     public double getOffsetZ() { | ||||||
|  |         return offsetZ; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * The extra value to set for the particle. Exactly what it does depends on the particle. | ||||||
|  |      * | ||||||
|  |      * @return <p>The particle's extra value</p> | ||||||
|  |      */ | ||||||
|  |     public double getExtra() { | ||||||
|  |         return extra; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,38 @@ | |||||||
|  | package net.knarcraft.knarlib.particle; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * The mode used for spawning one or more particle(s) | ||||||
|  |  */ | ||||||
|  | public enum ParticleMode { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns the set amount of particles on a single point in the world | ||||||
|  |      */ | ||||||
|  |     SINGLE, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns the set amount of particles in a square around the block | ||||||
|  |      */ | ||||||
|  |     SQUARE, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns the set amount of particles in a circle around the block | ||||||
|  |      */ | ||||||
|  |     CIRCLE, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns the set amount of particles in a pyramid centered on the block | ||||||
|  |      */ | ||||||
|  |     PYRAMID, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns the set amount of particles in a sphere centered on the block | ||||||
|  |      */ | ||||||
|  |     SPHERE, | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns the set amount of particles in a cube centered on the block | ||||||
|  |      */ | ||||||
|  |     CUBE, | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,142 @@ | |||||||
|  | package net.knarcraft.knarlib.particle; | ||||||
|  |  | ||||||
|  | import net.knarcraft.knarlib.util.ParticleHelper; | ||||||
|  | import org.bukkit.Location; | ||||||
|  | import org.bukkit.Material; | ||||||
|  | import org.bukkit.World; | ||||||
|  | import org.bukkit.block.Block; | ||||||
|  | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.UUID; | ||||||
|  | import java.util.function.Supplier; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A runnable tasks that spawns particles at the given blocks | ||||||
|  |  */ | ||||||
|  | @SuppressWarnings("unused") | ||||||
|  | public class ParticleSpawner implements Runnable { | ||||||
|  |  | ||||||
|  |     private final ParticleConfig particleConfig; | ||||||
|  |     private final Map<Material, ParticleConfig> materialConfigs; | ||||||
|  |  | ||||||
|  |     private Block processingBlock; | ||||||
|  |     private final Collection<Block> blocks; | ||||||
|  |     private final Supplier<Collection<Block>> blockSupplier; | ||||||
|  |     private final UUID storedCalculationId; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Instantiates a new particle spawner | ||||||
|  |      * | ||||||
|  |      * @param particleConfig  <p>The configuration for the particle to spawn</p> | ||||||
|  |      * @param materialConfigs <p>Extra particle configurations for specific materials</p> | ||||||
|  |      * @param blocks          <p>The blocks to spawn particles on</p> | ||||||
|  |      */ | ||||||
|  |     public ParticleSpawner(@NotNull ParticleConfig particleConfig, | ||||||
|  |                            @NotNull Map<Material, ParticleConfig> materialConfigs, @NotNull Collection<Block> blocks) { | ||||||
|  |         this.particleConfig = particleConfig; | ||||||
|  |         this.materialConfigs = materialConfigs; | ||||||
|  |         this.blocks = blocks; | ||||||
|  |         this.blockSupplier = null; | ||||||
|  |  | ||||||
|  |         this.storedCalculationId = UUID.randomUUID(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Instantiates a new particle spawner | ||||||
|  |      * | ||||||
|  |      * @param particleConfig  <p>The configuration for the particle to spawn</p> | ||||||
|  |      * @param materialConfigs <p>Extra particle configurations for specific materials</p> | ||||||
|  |      * @param blockSupplier   <p>The supplier supplying the blocks to spawn particles on</p> | ||||||
|  |      */ | ||||||
|  |     public ParticleSpawner(@NotNull ParticleConfig particleConfig, | ||||||
|  |                            @NotNull Map<Material, ParticleConfig> materialConfigs, | ||||||
|  |                            @NotNull Supplier<Collection<Block>> blockSupplier) { | ||||||
|  |         this.particleConfig = particleConfig; | ||||||
|  |         this.materialConfigs = materialConfigs; | ||||||
|  |         this.blocks = null; | ||||||
|  |         this.blockSupplier = blockSupplier; | ||||||
|  |  | ||||||
|  |         this.storedCalculationId = UUID.randomUUID(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets the id used for stored calculations | ||||||
|  |      * | ||||||
|  |      * <p>You should run ParticleHelper.clearStoredCalculations(id) with this id when discarding this particle spawner | ||||||
|  |      * to prevent a memory leak.</p> | ||||||
|  |      * | ||||||
|  |      * @return <p>The id used for stored calculations</p> | ||||||
|  |      */ | ||||||
|  |     public UUID getStoredCalculationId() { | ||||||
|  |         return this.storedCalculationId; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void run() { | ||||||
|  |         // Use the static collection of blocks, or a dynamically changing supplier | ||||||
|  |         Collection<Block> blocksToSpawnOn; | ||||||
|  |         if (blocks != null) { | ||||||
|  |             blocksToSpawnOn = blocks; | ||||||
|  |         } else if (blockSupplier != null) { | ||||||
|  |             blocksToSpawnOn = blockSupplier.get(); | ||||||
|  |         } else { | ||||||
|  |             throw new RuntimeException("There is a bug in the plugin code. Please contact the developer!"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for (Block block : blocksToSpawnOn) { | ||||||
|  |             // Ignore blocks in unloaded chunks | ||||||
|  |             if (!block.getChunk().isLoaded()) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             Location location = block.getLocation().clone(); | ||||||
|  |             World world = location.getWorld(); | ||||||
|  |             if (world == null) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             spawnParticleForBlock(block, world, location); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns the defined particle for the given block | ||||||
|  |      * | ||||||
|  |      * @param block    <p>The block to spawn a particle effect for</p> | ||||||
|  |      * @param world    <p>The world the block belongs to</p> | ||||||
|  |      * @param location <p>A clone of the block's location</p> | ||||||
|  |      */ | ||||||
|  |     private void spawnParticleForBlock(@NotNull Block block, @NotNull World world, @NotNull Location location) { | ||||||
|  |         // Store the currently processed block for height calculation | ||||||
|  |         processingBlock = block; | ||||||
|  |  | ||||||
|  |         double blockHeight = ParticleHelper.getBlockHeight(block); | ||||||
|  |         ParticleConfig activeConfig = getParticleConfig(); | ||||||
|  |  | ||||||
|  |         switch (getParticleConfig().getParticleMode()) { | ||||||
|  |             case SINGLE -> ParticleHelper.spawnParticle(world, location.clone().add(0.5, | ||||||
|  |                     activeConfig.getHeightOffset(), 0.5), activeConfig, blockHeight); | ||||||
|  |             case SQUARE -> ParticleHelper.drawSquare(world, location, activeConfig, blockHeight); | ||||||
|  |             case CIRCLE -> ParticleHelper.drawCircle(world, location, activeConfig, blockHeight, storedCalculationId); | ||||||
|  |             case PYRAMID -> ParticleHelper.drawPyramid(world, location, activeConfig, blockHeight, storedCalculationId); | ||||||
|  |             case SPHERE -> ParticleHelper.drawSphere(world, location, activeConfig, blockHeight, storedCalculationId); | ||||||
|  |             case CUBE -> ParticleHelper.drawCube(world, location, activeConfig, blockHeight); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets the particle config to use for the current block's material | ||||||
|  |      * | ||||||
|  |      * @return <p>The particle config to use</p> | ||||||
|  |      */ | ||||||
|  |     private ParticleConfig getParticleConfig() { | ||||||
|  |         ParticleConfig materialConfig = this.materialConfigs.get(processingBlock.getType()); | ||||||
|  |         if (materialConfig != null) { | ||||||
|  |             return materialConfig; | ||||||
|  |         } | ||||||
|  |         return this.particleConfig; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,116 @@ | |||||||
|  | package net.knarcraft.knarlib.particle; | ||||||
|  |  | ||||||
|  | import net.knarcraft.knarlib.util.ParticleHelper; | ||||||
|  | import org.bukkit.Bukkit; | ||||||
|  | import org.bukkit.Location; | ||||||
|  | import org.bukkit.Particle; | ||||||
|  | import org.bukkit.World; | ||||||
|  | import org.bukkit.entity.Player; | ||||||
|  | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Random; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.UUID; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A task for spawning trails behind players | ||||||
|  |  */ | ||||||
|  | @SuppressWarnings("unused") | ||||||
|  | public class ParticleTrailSpawner implements Runnable { | ||||||
|  |  | ||||||
|  |     private final Set<UUID> playersWithTrails = new HashSet<>(); | ||||||
|  |     private final Map<UUID, Particle> playerParticles = new HashMap<>(); | ||||||
|  |     private final Random random = new Random(); | ||||||
|  |     private final Particle particle; | ||||||
|  |     private final boolean randomTrailType; | ||||||
|  |     private final List<Particle> randomTrailTypes; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Instantiates a new particle trail spawner | ||||||
|  |      * | ||||||
|  |      * @param particle         <p>The type of particle used for the trail</p> | ||||||
|  |      * @param randomTrailType  <p>Whether to use a random trail type each time a player is launched</p> | ||||||
|  |      * @param randomTrailTypes <p>The types of particles to use for random trails</p> | ||||||
|  |      */ | ||||||
|  |     public ParticleTrailSpawner(@NotNull Particle particle, boolean randomTrailType, | ||||||
|  |                                 @NotNull List<Particle> randomTrailTypes) { | ||||||
|  |         this.particle = particle; | ||||||
|  |         this.randomTrailType = randomTrailType; | ||||||
|  |         this.randomTrailTypes = randomTrailTypes; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void run() { | ||||||
|  |         Set<UUID> offlinePlayers = new HashSet<>(); | ||||||
|  |         for (UUID playerId : playersWithTrails) { | ||||||
|  |             // Clear offline players from the list | ||||||
|  |             Player player = Bukkit.getPlayer(playerId); | ||||||
|  |             if (player == null) { | ||||||
|  |                 offlinePlayers.add(playerId); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             Location playerLocation = player.getLocation(); | ||||||
|  |             World playerWorld = playerLocation.getWorld(); | ||||||
|  |             if (playerWorld == null) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Decide on which type of particle to spawn | ||||||
|  |             Particle spawnParticle; | ||||||
|  |             if (randomTrailType) { | ||||||
|  |                 spawnParticle = playerParticles.get(playerId); | ||||||
|  |                 if (spawnParticle == null) { | ||||||
|  |                     spawnParticle = this.particle; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 spawnParticle = this.particle; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Spawn a trail particle | ||||||
|  |             ParticleConfig particleConfig = new ParticleConfig(ParticleMode.SINGLE, spawnParticle, | ||||||
|  |                     1, 1, 0, 0, 0, 0, 0); | ||||||
|  |             ParticleHelper.spawnParticle(playerWorld, playerLocation, particleConfig, 0); | ||||||
|  |         } | ||||||
|  |         playersWithTrails.removeAll(offlinePlayers); | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Removes the trail behind the player with the given id | ||||||
|  |      * | ||||||
|  |      * @param playerId <p>The id of the player to remove the trail for</p> | ||||||
|  |      */ | ||||||
|  |     public void removeTrail(UUID playerId) { | ||||||
|  |         this.playersWithTrails.remove(playerId); | ||||||
|  |         this.playerParticles.remove(playerId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Adds a trail behind the player with the given id | ||||||
|  |      * | ||||||
|  |      * @param playerId <p>The id of the player to add the trail to</p> | ||||||
|  |      */ | ||||||
|  |     public void startTrail(UUID playerId) { | ||||||
|  |         this.playerParticles.put(playerId, randomParticle()); | ||||||
|  |         this.playersWithTrails.add(playerId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets a random particle in the whitelist | ||||||
|  |      * | ||||||
|  |      * @return <p>A random particle</p> | ||||||
|  |      */ | ||||||
|  |     private Particle randomParticle() { | ||||||
|  |         Particle spawnParticle = null; | ||||||
|  |         while (spawnParticle == null || spawnParticle.getDataType() != Void.class) { | ||||||
|  |             spawnParticle = randomTrailTypes.get(random.nextInt(randomTrailTypes.size())); | ||||||
|  |         } | ||||||
|  |         return spawnParticle; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										242
									
								
								src/main/java/net/knarcraft/knarlib/util/ParticleHelper.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								src/main/java/net/knarcraft/knarlib/util/ParticleHelper.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,242 @@ | |||||||
|  | package net.knarcraft.knarlib.util; | ||||||
|  |  | ||||||
|  | import net.knarcraft.knarlib.particle.ParticleConfig; | ||||||
|  | import org.bukkit.Location; | ||||||
|  | import org.bukkit.World; | ||||||
|  | import org.bukkit.block.Block; | ||||||
|  | import org.bukkit.util.BoundingBox; | ||||||
|  | import org.bukkit.util.Vector; | ||||||
|  | import org.jetbrains.annotations.NotNull; | ||||||
|  |  | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.UUID; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A helper class for spawning particle effects | ||||||
|  |  */ | ||||||
|  | @SuppressWarnings("unused") | ||||||
|  | public final class ParticleHelper { | ||||||
|  |  | ||||||
|  |     private static final Map<UUID, Vector[]> pyramidVectors = new HashMap<>(); | ||||||
|  |     private static final Map<UUID, Double[][]> circleCoordinates = new HashMap<>(); | ||||||
|  |     private static final Map<UUID, Double[][]> sphereCoordinates = new HashMap<>(); | ||||||
|  |  | ||||||
|  |     private ParticleHelper() { | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Removes any stored calculations for the given id | ||||||
|  |      * | ||||||
|  |      * <p>If you frequently change the id without clearing, you'll create a memory leak!</p> | ||||||
|  |      * | ||||||
|  |      * @param id <p>The id specified when generating a shape</p> | ||||||
|  |      */ | ||||||
|  |     public static void clearStoredCalculations(UUID id) { | ||||||
|  |         pyramidVectors.remove(id); | ||||||
|  |         circleCoordinates.remove(id); | ||||||
|  |         sphereCoordinates.remove(id); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns a cube of particles at the given location | ||||||
|  |      * | ||||||
|  |      * <p>It is recommended to only spawn one particle with no spread, and a density between 1 and 0.01</p> | ||||||
|  |      * | ||||||
|  |      * @param world          <p>The world to spawn the particles in</p> | ||||||
|  |      * @param location       <p>The location of the block to spawn the particles at</p> | ||||||
|  |      * @param particleConfig <p>The configuration describing the particle to spawn</p> | ||||||
|  |      * @param blockHeight    <p>The height of the block to spawn the particle above</p> | ||||||
|  |      */ | ||||||
|  |     public static void drawCube(@NotNull World world, @NotNull Location location, @NotNull ParticleConfig particleConfig, | ||||||
|  |                                 double blockHeight) { | ||||||
|  |         // Draw the top and bottom of the cube | ||||||
|  |         drawSquare(world, location, particleConfig, blockHeight); | ||||||
|  |         drawSquare(world, location.clone().add(0, 1, 0), particleConfig, blockHeight); | ||||||
|  |  | ||||||
|  |         for (float y = 0; y <= 1; y += particleConfig.getParticleDensity()) { | ||||||
|  |             double height = particleConfig.getHeightOffset() + y; | ||||||
|  |             spawnParticle(world, location.clone().add(0, height, 0), particleConfig, blockHeight); | ||||||
|  |             spawnParticle(world, location.clone().add(0, height, 1), particleConfig, blockHeight); | ||||||
|  |             spawnParticle(world, location.clone().add(1, height, 0), particleConfig, blockHeight); | ||||||
|  |             spawnParticle(world, location.clone().add(1, height, 1), particleConfig, blockHeight); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns a sphere of particles at the given location | ||||||
|  |      * | ||||||
|  |      * <p>It is recommended to only spawn one particle with no spread, and a density between 4 and 0.1</p> | ||||||
|  |      * | ||||||
|  |      * @param world                <p>The world to spawn the particles in</p> | ||||||
|  |      * @param location             <p>The location of the block to spawn the particles at</p> | ||||||
|  |      * @param particleConfig       <p>The configuration describing the particle to spawn</p> | ||||||
|  |      * @param blockHeight          <p>The height of the block to spawn the particle above</p> | ||||||
|  |      * @param storedCalculationsId <p>An id for stored calculations. Use clearStoredCalculations with this id later.</p> | ||||||
|  |      */ | ||||||
|  |     public static void drawSphere(@NotNull World world, @NotNull Location location, @NotNull ParticleConfig particleConfig, | ||||||
|  |                                   double blockHeight, UUID storedCalculationsId) { | ||||||
|  |         // For spheres, densities below 0.1 has weird bugs such as blinking in and out of existence, and floating point | ||||||
|  |         // errors when calculating the length of circleCoordinates | ||||||
|  |         double density = Math.max(1, particleConfig.getParticleDensity()); | ||||||
|  |         // Store calculations for improved efficiency | ||||||
|  |         if (sphereCoordinates.get(storedCalculationsId) == null) { | ||||||
|  |             int length = (int) Math.ceil((180 / density)); | ||||||
|  |             Double[][] coordinates = new Double[length * 3][]; | ||||||
|  |             double height = particleConfig.getHeightOffset() + 0.5; | ||||||
|  |             int i = 0; | ||||||
|  |             for (float x = 0; x < 180; x += density) { | ||||||
|  |                 if (i >= coordinates.length) { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 double cos = 0.5 * Math.cos(x); | ||||||
|  |                 double sin = 0.5 * Math.sin(x); | ||||||
|  |                 coordinates[i++] = new Double[]{sin + 0.5, height, cos + 0.5}; | ||||||
|  |                 coordinates[i++] = new Double[]{sin + 0.5, height + cos, 0.5}; | ||||||
|  |                 coordinates[i++] = new Double[]{0.5, height + sin, cos + 0.5}; | ||||||
|  |             } | ||||||
|  |             sphereCoordinates.put(storedCalculationsId, coordinates); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Spawn particles on the stored locations, relative to the launchpad | ||||||
|  |         for (Double[] sphereCoordinate : sphereCoordinates.get(storedCalculationsId)) { | ||||||
|  |             spawnParticle(world, location.clone().add(sphereCoordinate[0], sphereCoordinate[1], sphereCoordinate[2]), | ||||||
|  |                     particleConfig, blockHeight); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns a pyramid of particles at the given location | ||||||
|  |      * | ||||||
|  |      * <p>It is recommended to only spawn one particle with no spread, and a density between 1 and 0.01</p> | ||||||
|  |      * | ||||||
|  |      * @param world                <p>The world to spawn the particles in</p> | ||||||
|  |      * @param location             <p>The location of the block to spawn the particles at</p> | ||||||
|  |      * @param particleConfig       <p>The configuration describing the particle to spawn</p> | ||||||
|  |      * @param blockHeight          <p>The height of the block to spawn the particle above</p> | ||||||
|  |      * @param storedCalculationsId <p>An id for stored calculations. Use clearStoredCalculations with this id later.</p> | ||||||
|  |      */ | ||||||
|  |     public static void drawPyramid(@NotNull World world, @NotNull Location location, @NotNull ParticleConfig particleConfig, | ||||||
|  |                                    double blockHeight, UUID storedCalculationsId) { | ||||||
|  |         // Draw the bottom of the pyramid | ||||||
|  |         drawSquare(world, location, particleConfig, blockHeight); | ||||||
|  |  | ||||||
|  |         // Store calculations for improved efficiency | ||||||
|  |         if (pyramidVectors.get(storedCalculationsId) == null) { | ||||||
|  |             // The 0.5 offsets are required for the angle of the pyramid's 4 lines to be correct | ||||||
|  |             double height = particleConfig.getHeightOffset(); | ||||||
|  |             double coordinateMin = -0.5 * height; | ||||||
|  |             double coordinateMax = 1 + (0.5 * height); | ||||||
|  |  | ||||||
|  |             Vector[] vectors = new Vector[5]; | ||||||
|  |             // The vector from the origin to the top of the pyramid | ||||||
|  |             vectors[0] = new Vector(0.5, 1 + height, 0.5); | ||||||
|  |             // The vectors from the top of the pyramid towards each corner | ||||||
|  |             vectors[1] = new Vector(coordinateMin, 0, coordinateMin).subtract(vectors[0]).normalize(); | ||||||
|  |             vectors[2] = new Vector(coordinateMax, 0, coordinateMin).subtract(vectors[0]).normalize(); | ||||||
|  |             vectors[3] = new Vector(coordinateMin, 0, coordinateMax).subtract(vectors[0]).normalize(); | ||||||
|  |             vectors[4] = new Vector(coordinateMax, 0, coordinateMax).subtract(vectors[0]).normalize(); | ||||||
|  |             pyramidVectors.put(storedCalculationsId, vectors); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Vector[] storedVectors = pyramidVectors.get(storedCalculationsId); | ||||||
|  |         Location topLocation = location.clone().add(storedVectors[0]); | ||||||
|  |         for (double x = 0; x <= 1.2; x += particleConfig.getParticleDensity()) { | ||||||
|  |             spawnParticle(world, topLocation.clone().add(storedVectors[1].clone().multiply(x)), particleConfig, blockHeight); | ||||||
|  |             spawnParticle(world, topLocation.clone().add(storedVectors[2].clone().multiply(x)), particleConfig, blockHeight); | ||||||
|  |             spawnParticle(world, topLocation.clone().add(storedVectors[3].clone().multiply(x)), particleConfig, blockHeight); | ||||||
|  |             spawnParticle(world, topLocation.clone().add(storedVectors[4].clone().multiply(x)), particleConfig, blockHeight); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns a circle of particles at the given location | ||||||
|  |      * | ||||||
|  |      * <p>It is recommended to only spawn one particle with no spread, and a density between 4 and 0.1</p> | ||||||
|  |      * | ||||||
|  |      * @param world                <p>The world to spawn the particles in</p> | ||||||
|  |      * @param location             <p>The location of the block to spawn the particles at</p> | ||||||
|  |      * @param particleConfig       <p>The configuration describing the particle to spawn</p> | ||||||
|  |      * @param blockHeight          <p>The height of the block to spawn the particle above</p> | ||||||
|  |      * @param storedCalculationsId <p>An id for stored calculations. Use clearStoredCalculations with this id later.</p> | ||||||
|  |      */ | ||||||
|  |     public static void drawCircle(@NotNull World world, @NotNull Location location, @NotNull ParticleConfig particleConfig, | ||||||
|  |                                   double blockHeight, UUID storedCalculationsId) { | ||||||
|  |         // For circles, densities below 0.1 has weird bugs such as blinking in and out of existence, and floating point | ||||||
|  |         // errors when calculating the length of circleCoordinates | ||||||
|  |         double density = Math.max(1, particleConfig.getParticleDensity()); | ||||||
|  |         // Store calculations for improved efficiency | ||||||
|  |         if (circleCoordinates.get(storedCalculationsId) == null) { | ||||||
|  |             Double[][] coordinates = new Double[(int) Math.ceil((180 / density))][]; | ||||||
|  |             int i = 0; | ||||||
|  |             for (float x = 0; x < 180; x += density) { | ||||||
|  |                 if (i >= coordinates.length) { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 coordinates[i++] = new Double[]{(0.5 * Math.sin(x)) + 0.5, (0.5 * Math.cos(x)) + 0.5}; | ||||||
|  |             } | ||||||
|  |             circleCoordinates.put(storedCalculationsId, coordinates); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Spawn particles on the stored locations, relative to the launchpad | ||||||
|  |         for (Double[] circleCoordinate : circleCoordinates.get(storedCalculationsId)) { | ||||||
|  |             spawnParticle(world, location.clone().add(circleCoordinate[0], particleConfig.getHeightOffset(), | ||||||
|  |                     circleCoordinate[1]), particleConfig, blockHeight); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns a square of particles at the given location | ||||||
|  |      * | ||||||
|  |      * <p>It is recommended to only spawn one particle with no spread, and a density between 1 and 0.01</p> | ||||||
|  |      * | ||||||
|  |      * @param world          <p>The world to spawn the particles in</p> | ||||||
|  |      * @param location       <p>The location of the block to spawn the particles at</p> | ||||||
|  |      * @param particleConfig <p>The configuration describing the particle to spawn</p> | ||||||
|  |      * @param blockHeight    <p>The height of the block to spawn the particle above</p> | ||||||
|  |      */ | ||||||
|  |     public static void drawSquare(@NotNull World world, @NotNull Location location, @NotNull ParticleConfig particleConfig, | ||||||
|  |                                   double blockHeight) { | ||||||
|  |         for (float x = 0; x <= 1; x += particleConfig.getParticleDensity()) { | ||||||
|  |             spawnParticle(world, location.clone().add(x, particleConfig.getHeightOffset(), 0), particleConfig, blockHeight); | ||||||
|  |             spawnParticle(world, location.clone().add(x, particleConfig.getHeightOffset(), 1), particleConfig, blockHeight); | ||||||
|  |             spawnParticle(world, location.clone().add(0, particleConfig.getHeightOffset(), x), particleConfig, blockHeight); | ||||||
|  |             spawnParticle(world, location.clone().add(1, particleConfig.getHeightOffset(), x), particleConfig, blockHeight); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Spawns the specified particle at the given location | ||||||
|  |      * | ||||||
|  |      * @param world          <p>The world to spawn the particle in</p> | ||||||
|  |      * @param location       <p>The location to spawn the particle at</p> | ||||||
|  |      * @param particleConfig <p>The configuration describing the particle to spawn</p> | ||||||
|  |      * @param blockHeight    <p>The height of the block to spawn the particle above</p> | ||||||
|  |      */ | ||||||
|  |     public static void spawnParticle(@NotNull World world, @NotNull Location location, | ||||||
|  |                                      @NotNull ParticleConfig particleConfig, double blockHeight) { | ||||||
|  |         world.spawnParticle(particleConfig.getParticleType(), | ||||||
|  |                 location.add(0, blockHeight, 0), particleConfig.getParticleAmount(), | ||||||
|  |                 particleConfig.getOffsetX(), particleConfig.getOffsetY(), particleConfig.getOffsetZ(), | ||||||
|  |                 particleConfig.getExtra()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Gets the height of the block at the given location | ||||||
|  |      * | ||||||
|  |      * @param block <p>The block to check</p> | ||||||
|  |      * @return <p>The height of the block</p> | ||||||
|  |      */ | ||||||
|  |     public static double getBlockHeight(Block block) { | ||||||
|  |         double maxY = 0; | ||||||
|  |         for (BoundingBox boundingBox : block.getCollisionShape().getBoundingBoxes()) { | ||||||
|  |             if (boundingBox.getMaxY() > maxY) { | ||||||
|  |                 maxY = boundingBox.getMaxY(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return maxY; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user