From e528ce96b5c5797dceca9e1c643119e8c8a3b5f2 Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Tue, 4 Jul 2023 15:12:26 +0200 Subject: [PATCH] Adds several particle improvements Adds a new sphere particle mode Stores particle calculations for improved efficiency Makes particles take the height of the launchpad block into account Sets a limit for circular density as too many particles create visual glitches. Makes the particle respawn time configurable --- .../config/LaunchpadConfiguration.java | 3 +- .../launchpad/config/ParticleMode.java | 7 +- .../launchpad/task/ParticleSpawner.java | 130 +++++++++++++++--- src/main/resources/config.yml | 13 +- 4 files changed, 125 insertions(+), 28 deletions(-) diff --git a/src/main/java/net/knarcraft/launchpad/config/LaunchpadConfiguration.java b/src/main/java/net/knarcraft/launchpad/config/LaunchpadConfiguration.java index 38d4341..9a58490 100644 --- a/src/main/java/net/knarcraft/launchpad/config/LaunchpadConfiguration.java +++ b/src/main/java/net/knarcraft/launchpad/config/LaunchpadConfiguration.java @@ -161,6 +161,7 @@ public class LaunchpadConfiguration { double heightOffset = particlesSection.getDouble("heightOffset", 0.5); double particleDensity = particlesSection.getDouble("particleDensity", 0.1); double extra = particlesSection.getDouble("extra", 0); + int spawnDelay = particlesSection.getInt("spawnDelay", 20); ParticleMode particleMode; try { particleMode = ParticleMode.valueOf(particlesSection.getString("mode")); @@ -184,7 +185,7 @@ public class LaunchpadConfiguration { // Start particle spawning particleTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Launchpad.getInstance(), new ParticleSpawner(particleMode, particleType, particleAmount, particleDensity, heightOffset, - offsetX, offsetY, offsetZ, extra), 20, 20); + offsetX, offsetY, offsetZ, extra), 20, spawnDelay); } } diff --git a/src/main/java/net/knarcraft/launchpad/config/ParticleMode.java b/src/main/java/net/knarcraft/launchpad/config/ParticleMode.java index ca85bf4..bfb6169 100644 --- a/src/main/java/net/knarcraft/launchpad/config/ParticleMode.java +++ b/src/main/java/net/knarcraft/launchpad/config/ParticleMode.java @@ -21,8 +21,13 @@ public enum ParticleMode { CIRCLE, /** - * Spawns the set amount of particles in a triangle centered on the block + * 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, + } diff --git a/src/main/java/net/knarcraft/launchpad/task/ParticleSpawner.java b/src/main/java/net/knarcraft/launchpad/task/ParticleSpawner.java index d2c0508..192bcf8 100644 --- a/src/main/java/net/knarcraft/launchpad/task/ParticleSpawner.java +++ b/src/main/java/net/knarcraft/launchpad/task/ParticleSpawner.java @@ -6,6 +6,7 @@ import net.knarcraft.launchpad.launchpad.LaunchpadBlockHandler; import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.World; +import org.bukkit.util.BoundingBox; import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; @@ -24,6 +25,12 @@ public class ParticleSpawner implements Runnable { private final double offsetZ; private final double extra; + private Vector[] pyramidVectors; + private double[][] circleCoordinates; + private double[][] sphereCoordinates; + + private LaunchpadBlock processingLaunchpad = null; + /** * Instantiates a new particle spawner * @@ -49,11 +56,15 @@ public class ParticleSpawner implements Runnable { this.offsetY = offsetY; this.offsetZ = offsetZ; this.extra = extra; + + this.pyramidVectors = null; + this.circleCoordinates = null; } @Override public void run() { for (LaunchpadBlock launchpad : LaunchpadBlockHandler.getAll()) { + // Ignore launchpads in unloaded chunks if (!launchpad.getBlock().getChunk().isLoaded()) { continue; } @@ -64,15 +75,53 @@ public class ParticleSpawner implements Runnable { continue; } + // Store the currently processed launchpad for height calculation + processingLaunchpad = launchpad; + switch (particleMode) { case SINGLE -> spawnParticle(world, location.clone().add(0.5, heightOffset, 0.5)); case SQUARE -> drawSquare(world, location); case CIRCLE -> drawCircle(world, location); case PYRAMID -> drawPyramid(world, location); + case SPHERE -> drawSphere(world, location); } } } + /** + * Spawns a sphere of particles at the given location + * + * @param world

The world to spawn the particles in

+ * @param location

The location of the block to spawn the particles at

+ */ + private void drawSphere(@NotNull World world, @NotNull Location location) { + // 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, particleDensity); + // Store calculations for improved efficiency + if (sphereCoordinates == null) { + int length = (int) Math.ceil((180 / density)); + sphereCoordinates = new double[length * 3][]; + int i = 0; + for (float x = 0; x < 180; x += density) { + if (i >= sphereCoordinates.length) { + continue; + } + sphereCoordinates[i++] = new double[]{(0.5 * Math.sin(x)) + 0.5, + heightOffset + 0.5, (0.5 * Math.cos(x)) + 0.5}; + sphereCoordinates[i++] = new double[]{(0.5 * Math.sin(x)) + 0.5, + heightOffset + 0.5 + (0.5 * Math.cos(x)), 0.5}; + sphereCoordinates[i++] = new double[]{0.5, + heightOffset + 0.5 + (0.5 * Math.sin(x)), (0.5 * Math.cos(x)) + 0.5}; + } + } + + // Spawn particles on the stored locations, relative to the launchpad + for (double[] sphereCoordinate : sphereCoordinates) { + spawnParticle(world, location.clone().add(sphereCoordinate[0], sphereCoordinate[1], sphereCoordinate[2])); + } + } + /** * Spawns a pyramid of particles at the given location * @@ -80,24 +129,31 @@ public class ParticleSpawner implements Runnable { * @param location

The location of the block to spawn the particles at

*/ private void drawPyramid(@NotNull World world, @NotNull Location location) { - // Draw the bottom of the + // Draw the bottom of the pyramid drawSquare(world, location); - // Top: 0.5, 1, 0.5. Corner 1: 0, 0, 0. Corner 2: 1, 0, 0. Corner 3: 0, 0, 1, Corner 4: 1, 0, 1 - double triangleHeight = 1; - Vector topVector = new Vector(0.5, triangleHeight + heightOffset, 0.5); - Location topLocation = location.clone().add(topVector); - double coordinateMin = -0.5 * heightOffset; - double coordinateMax = 1 + (0.5 * heightOffset); - Vector line1Direction = new Vector(coordinateMin, 0, coordinateMin).subtract(topVector).normalize(); - Vector line2Direction = new Vector(coordinateMax, 0, coordinateMin).subtract(topVector).normalize(); - Vector line3Direction = new Vector(coordinateMin, 0, coordinateMax).subtract(topVector).normalize(); - Vector line4Direction = new Vector(coordinateMax, 0, coordinateMax).subtract(topVector).normalize(); - for (double x = 0; x <= triangleHeight; x += particleDensity) { - spawnParticle(world, topLocation.clone().add(line1Direction.clone().multiply(x))); - spawnParticle(world, topLocation.clone().add(line2Direction.clone().multiply(x))); - spawnParticle(world, topLocation.clone().add(line3Direction.clone().multiply(x))); - spawnParticle(world, topLocation.clone().add(line4Direction.clone().multiply(x))); + // Store calculations for improved efficiency + if (pyramidVectors == null) { + // The 0.5 offsets are required for the angle of the pyramid's 4 lines to be correct + double coordinateMin = -0.5 * heightOffset; + double coordinateMax = 1 + (0.5 * heightOffset); + + pyramidVectors = new Vector[5]; + // The vector from the origin to the top of the pyramid + pyramidVectors[0] = new Vector(0.5, 1 + heightOffset, 0.5); + // The vectors from the top of the pyramid towards each corner + pyramidVectors[1] = new Vector(coordinateMin, 0, coordinateMin).subtract(pyramidVectors[0]).normalize(); + pyramidVectors[2] = new Vector(coordinateMax, 0, coordinateMin).subtract(pyramidVectors[0]).normalize(); + pyramidVectors[3] = new Vector(coordinateMin, 0, coordinateMax).subtract(pyramidVectors[0]).normalize(); + pyramidVectors[4] = new Vector(coordinateMax, 0, coordinateMax).subtract(pyramidVectors[0]).normalize(); + } + + Location topLocation = location.clone().add(pyramidVectors[0]); + for (double x = 0; x <= 1; x += particleDensity) { + spawnParticle(world, topLocation.clone().add(pyramidVectors[1].clone().multiply(x))); + spawnParticle(world, topLocation.clone().add(pyramidVectors[2].clone().multiply(x))); + spawnParticle(world, topLocation.clone().add(pyramidVectors[3].clone().multiply(x))); + spawnParticle(world, topLocation.clone().add(pyramidVectors[4].clone().multiply(x))); } } @@ -108,9 +164,24 @@ public class ParticleSpawner implements Runnable { * @param location

The location of the block to spawn the particles at

*/ private void drawCircle(@NotNull World world, @NotNull Location location) { - for (float x = 0; x < 180; x += particleDensity) { - spawnParticle(world, location.clone().add((0.5 * Math.sin(x)) + 0.5, heightOffset, - (0.5 * Math.cos(x)) + 0.5)); + // 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, particleDensity); + // Store calculations for improved efficiency + if (circleCoordinates == null) { + circleCoordinates = new double[(int) Math.ceil((180 / density))][]; + int i = 0; + for (float x = 0; x < 180; x += density) { + if (i >= circleCoordinates.length) { + continue; + } + circleCoordinates[i++] = new double[]{(0.5 * Math.sin(x)) + 0.5, (0.5 * Math.cos(x)) + 0.5}; + } + } + + // Spawn particles on the stored locations, relative to the launchpad + for (double[] circleCoordinate : circleCoordinates) { + spawnParticle(world, location.clone().add(circleCoordinate[0], heightOffset, circleCoordinate[1])); } } @@ -121,7 +192,7 @@ public class ParticleSpawner implements Runnable { * @param location

The location of the block to spawn the particles at

*/ private void drawSquare(@NotNull World world, @NotNull Location location) { - for (float x = 0; x < 1; x += particleDensity) { + for (float x = 0; x <= 1; x += particleDensity) { spawnParticle(world, location.clone().add(x, heightOffset, 0)); spawnParticle(world, location.clone().add(x, heightOffset, 1)); spawnParticle(world, location.clone().add(0, heightOffset, x)); @@ -136,7 +207,24 @@ public class ParticleSpawner implements Runnable { * @param location

The location to spawn the particle at

*/ private void spawnParticle(@NotNull World world, @NotNull Location location) { - world.spawnParticle(particleType, location, particleAmount, offsetX, offsetY, offsetZ, extra); + world.spawnParticle(particleType, location.add(0, getBlockHeight(processingLaunchpad), 0), particleAmount, + offsetX, offsetY, offsetZ, extra); + } + + /** + * Gets the height of the launchpad block at the given location + * + * @param launchpad

The launchpad to check

+ * @return

The height of the block

+ */ + private double getBlockHeight(LaunchpadBlock launchpad) { + double maxY = 0; + for (BoundingBox boundingBox : launchpad.getBlock().getCollisionShape().getBoundingBoxes()) { + if (boundingBox.getMaxY() > maxY) { + maxY = boundingBox.getMaxY(); + } + } + return maxY; } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 3322775..cb9c7f4 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -25,17 +25,17 @@ launchpad: particles: # Whether to enable particles above launchpads enabled: false - # The mode used for spawning particles. Valid values are: SINGLE, SQUARE and CIRCLE + # The mode used for spawning particles. Valid values are: SINGLE, SQUARE, PYRAMID, SPHERE and CIRCLE mode: SQUARE # The type of particle to spawn. See https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html type: DRIP_LAVA # The number of particles to spawn every second amount: 1 - # The offset of the particle in the X direction, relative to the launchpad + # The offset, or spread of the particles in the X direction, relative to the launchpad offsetX: 0 - # The offset of the particle in the Y direction, relative to the launchpad + # The offset, or spread of the particles in the Y direction, relative to the launchpad offsetY: 0 - # The offset of the particle in the Z direction, relative to the launchpad + # The offset, or spread of the particles in the Z direction, relative to the launchpad offsetZ: 0 # The height above the launchpad the particle should spawn. 1 = one block above, 0.5 = half a block above heightOffset: 0.5 @@ -44,4 +44,7 @@ launchpad: # particles on each side. particleDensity: 0.5 # Extra data for the particle. Valid values depend on the particle type. - extra: 0 \ No newline at end of file + extra: 0 + # The amount of ticks between each time the particle(s) should be set again. Depending on the particle, higher + # values will make the particle(s) completely disappear and reappear. + spawnDelay: 20 \ No newline at end of file