Adds code for spawning customizable particle and trail effects
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good

This commit is contained in:
2023-07-08 15:20:59 +02:00
parent 4fb4429f8b
commit ad5dabc712
5 changed files with 705 additions and 0 deletions

View 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;
}
}

View File

@@ -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,
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}