Paper Plugin Guide
File mode0

Paper essentials

Potion effects, particles and effects

Add potion effects, draw with particles and create explosions, lightning and fireworks.

Beginner39 min read

Potion effects, particles, fireworks and lightning are what make a server feel alive. On this page you will give players speed and night vision, spawn colorful particles, draw a spinning ring and helix around a player with a little math, set off safe explosions and fireworks, and finish with an /aura command.

Two kinds of flair

Effects in Minecraft come in two flavors, and it helps to keep them apart:

  • Effects that change the game: a potion effect like Speed or Regeneration really changes how fast a player moves or how quickly they heal. It lasts for some time and then wears off.
  • Effects that only look or sound like something: a particle (a tiny flying picture, like smoke or a heart) and a sound do not change anything. They are packets the server sends to players' screens, and they are gone a moment later.

Because particles and sounds cost the server almost nothing, plugins use them freely to give feedback: a heart burst when you heal, a puff of smoke when a spell fizzles. Use them the way a game designer does, to tell the player "that worked".

Potion effects

A PotionEffect is a description of one effect: which one, how long, and how strong. You make one with new PotionEffect(...) and give it to a living entity with addPotionEffect.

new PotionEffect(type, duration, amplifier, ambient, particles, icon) type SPEED which effect duration 600 ticks (30 s) amplifier 1 level II 3 switches true / false ambient, particles, icon Watch out: the amplifier starts at 0 0 is level I, 1 is level II, 2 is level III. And 20 ticks make one second.
The parts of a PotionEffect. The amplifier counts from zero, and the duration is in ticks.

The first three values are the ones you always give:

  • Type: a constant from PotionEffectType, such as SPEED, SLOWNESS, STRENGTH, JUMP_BOOST, REGENERATION, NIGHT_VISION or GLOWING. Type PotionEffectType. in IntelliJ and read the autocomplete list for the rest.
  • Duration: how long, in ticks. A tick is one twentieth of a second, so 20 ticks make one second and 600 ticks make 30 seconds. Writing 30 * 20 instead of 600 makes the code easier to read. The Tick Calculator converts for you.
  • Amplifier: how strong, counted from zero. Amplifier 0 is "Speed I", amplifier 1 is "Speed II", amplifier 2 is "Speed III". This is the number one beginner surprise.

The last three are on-off switches. ambient makes the particles fainter, the way a beacon's effect looks. particles hides or shows the swirling particles. icon hides or shows the little picture in the corner of the screen. Leave them out and the game picks defaults: the three-number form new PotionEffect(type, duration, amplifier) sets ambient to true, so the particles look faint, while the icon and particles are shown. Give all six values when you want full control.

BuffCommand.javaCompiles on Paper 26.3Compile Lab
Where this file liveseffects-particles-sounds-potionssrcmainjavacomexampleeffectsparticlessoundspotionsBuffCommand.java

The package com.example.effectsparticlessoundspotions is the folder path com/example/effectsparticlessoundspotions inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • effects-particles-sounds-potions/
    • src/main/
      • java/com/example/effectsparticlessoundspotions/Package com.example.effectsparticlessoundspotions
        • BuffCommand.javayou are hereCommand (BasicCommand)
        • PotionsPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.effectsparticlessoundspotions;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import org.bukkit.entity.Player;6import org.bukkit.potion.PotionEffect;7import org.bukkit.potion.PotionEffectType;8 9public final class BuffCommand implements BasicCommand {10 11    private static final int TICKS_PER_SECOND = 20;12 13    @Override14    public void execute(CommandSourceStack source, String[] args) {15        if (!(source.getExecutor() instanceof Player player)) {16            source.getSender().sendRichMessage("<red>Only players can be buffed.");17            return;18        }19        String mode = args.length > 0 ? args[0].toLowerCase() : "normal";20        switch (mode) {21            case "clear" -> clearBuffs(player);22            case "forever" -> giveForeverBuff(player);23            default -> giveBuffs(player);24        }25    }26 27    private void giveBuffs(Player player) {28        PotionEffect speed = new PotionEffect(PotionEffectType.SPEED, seconds(30), 1);29        PotionEffect nightVision = new PotionEffect(PotionEffectType.NIGHT_VISION, seconds(120), 0, false, false, false);30        player.addPotionEffect(speed);31        player.addPotionEffect(nightVision);32        player.sendRichMessage("<green>Speed II for 30 seconds and night vision for 2 minutes.");33    }34 35    private void giveForeverBuff(Player player) {36        player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP_BOOST, PotionEffect.INFINITE_DURATION, 1));37        player.sendRichMessage("<aqua>Jump Boost II with no end. Use <white>/buff clear</white> to stop it.");38    }39 40    private void clearBuffs(Player player) {41        player.removePotionEffect(PotionEffectType.SPEED);42        player.removePotionEffect(PotionEffectType.NIGHT_VISION);43        player.removePotionEffect(PotionEffectType.JUMP_BOOST);44        player.sendRichMessage("<yellow>Your buffs are gone.");45    }46 47    private int seconds(int seconds) {48        return seconds * TICKS_PER_SECOND;49    }50}
  1. A named constant, so nobody wonders what the 20 means.
  2. Reads the first word the player typed after the command, or uses "normal" when they typed nothing. See Commands, part 1 for how arguments work.
  3. Picks one of three actions. default runs when the word is anything else.
  4. Speed, for 30 seconds, amplifier 1, which is Speed II.
  5. Night vision for two minutes at level I. The three false values are ambient, particles and icon: not faint, no swirling particles and no icon, so the player is not distracted.
  6. Gives the effect to the player. If the player already has a stronger or longer-lasting version of that effect, the game keeps that one active.
  7. A special value, -1, that means "never runs out". The effect stays until you remove it, or the player drinks milk or dies.
  8. Takes that one effect away. Nothing happens if the player does not have it. clearActivePotionEffects() removes every effect at once.
  9. A helper method that turns seconds into ticks. Tiny methods like this keep the real code readable (see Methods).
Speed II for 30 seconds and night vision for 2 minutes.
Jump Boost II with no end. Use /buff clear to stop it.

Useful methods when you work with effects:

CallWhat it does
entity.addPotionEffect(effect)Adds an effect. Works on any LivingEntity, so zombies and cows can have effects too.
entity.hasPotionEffect(type)true if the entity currently has that kind of effect.
entity.getPotionEffect(type)The effect itself (or null), so you can read getDuration() and getAmplifier().
entity.removePotionEffect(type)Removes it.
entity.clearActivePotionEffects()Removes all effects.

Particles

A Particle is one of Minecraft's built-in tiny pictures: FLAME, HEART, HAPPY_VILLAGER (the green sparkles), END_ROD, SMOKE, CLOUD, CRIT and many more. You spawn them with world.spawnParticle(...), and everyone near the spot sees them. To show them to one player only, call player.spawnParticle(...) with the same arguments.

The most useful form looks like this:

spawnParticle, step by step
1world.spawnParticle(Particle.FLAME, location, 30, 0.3, 0.3, 0.3, 0.02);
  1. Which particle.
  2. Where the center of the cloud is.
  3. How many particles to spawn. One call can draw a whole cloud.
  4. The spread. Each particle lands a random distance, up to about this much, away from the center on the x, y and z axes. Use 0 for all three to get a single exact point.
  5. The "extra" value. For most particles it is their speed. Try 0 for particles that hang still.

Particles with extra data

A few particles need to know more before they can draw. A DUST particle needs a color and a size. A BLOCK particle needs to know which block's crumbs to draw. You pass that extra information as the very last argument:

  • Particle.DUST takes new Particle.DustOptions(Color.PURPLE, 1.5f): a color and a size.
  • Particle.BLOCK takes block data, such as Material.DIAMOND_BLOCK.createBlockData().
  • Particle.DUST_COLOR_TRANSITION takes a Particle.DustTransition, a dust that fades from one color to another.

The command below shows four ways to spawn. The fourth uses the particle builder, which spells out each setting by name, so you do not have to remember the order of seven numbers:

ParticlesCommand.javaCompiles on Paper 26.3Compile Lab
Where this file liveseffects-particles-sounds-particlessrcmainjavacomexampleeffectsparticlessoundsparticlesParticlesCommand.java

The package com.example.effectsparticlessoundsparticles is the folder path com/example/effectsparticlessoundsparticles inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • effects-particles-sounds-particles/
    • src/main/
      • java/com/example/effectsparticlessoundsparticles/Package com.example.effectsparticlessoundsparticles
        • ParticlesCommand.javayou are hereCommand (BasicCommand)
        • ParticlesPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

26switch (args[0].toLowerCase()) {27    case "flame" -> world.spawnParticle(Particle.FLAME, spot, 30, 0.3, 0.3, 0.3, 0.02);28    case "dust" -> world.spawnParticle(Particle.DUST, spot, 40, 0.4, 0.4, 0.4,29        new Particle.DustOptions(Color.PURPLE, 1.5f));30    case "block" -> world.spawnParticle(Particle.BLOCK, spot, 40, 0.3, 0.3, 0.3,31        Material.DIAMOND_BLOCK.createBlockData());32    case "hearts" -> Particle.HEART.builder()33        .location(spot)34        .count(6)35        .offset(0.4, 0.4, 0.4)36        .spawn();37    default -> player.sendRichMessage("<red>Unknown particle. Try flame, dust, block or hearts.");38}
  1. A cloud of 30 flames with a little spread and speed.
  2. Colored dust. The color and size come from the last argument.
  3. Purple dust, one and a half times the normal size. Note that Color here is org.bukkit.Color, not an Adventure color.
  4. Turns a material into block data, so the particles look like chips of a diamond block.
  5. Starts a builder for hearts. Each line after it sets one thing, and spawn() at the end sends the particles.
  6. The same spread as before, but now with a name that says what it is.
Use /particles flame, dust, block or hearts.

Drawing shapes with math

One particle in the air looks dull. Many particles in a pattern look like magic. To draw a pattern you need a way to turn "the third point of a circle" into x and z coordinates. That is where sine and cosine come in. You do not need to understand them deeply; you need the recipe.

A circle

Pick a center, a radius, and how many points you want. Walk around the circle in equal steps. A full turn is 2 * Math.PI (about 6.28) in the units Java uses for angles, which are called radians. For each point:

x z center point x z Sideways (x) cx + r * cos(angle) Forward (z) cz + r * sin(angle) Whole circle angle = 2 * PI * i / points
Cosine tells you how far sideways to go from the center, sine how far forward. The angle picks the point on the circle.

First see it in plain Java. This program prints the eight points of a circle with radius 2 around the spot x=100, z=50:

CircleMath.javaRuns on Java 25
1void main() {2    double centerX = 100.0;3    double centerZ = 50.0;4    double radius = 2.0;5    int points = 8;6 7    for (int i = 0; i < points; i++) {8        double angle = 2 * Math.PI * i / points;9        double x = centerX + radius * Math.cos(angle);10        double z = centerZ + radius * Math.sin(angle);11        int degrees = 360 * i / points;12        IO.println(String.format("point %d at %3d degrees: x = %6.2f, z = %6.2f", i, degrees, x, z));13    }14}
  1. The angle of point number i. Point 0 is at angle 0, and point 4 of 8 is exactly halfway around.
  2. Cosine gives a number between -1 and 1: how far sideways, as a fraction of the radius.
  3. Sine gives the matching forward and backward fraction.
  4. Multiply by the radius and add the center, because the circle is around the center and not around zero. See Operators and math.

Look at the output: the points step from x=102 (right of the center) to x=98 (left of it), and z goes up and down the same way. Each pair is a spot on a perfect circle.

A helix

A helix is the shape of a spring or a DNA strand. It is a circle that also rises. Each step turns a little and goes up a little:

HelixMath.javaRuns on Java 25
1void main() {2    double radius = 1.0;3    double height = 2.0;4    int steps = 8;5    double radiansPerStep = 0.5;6 7    for (int step = 0; step <= steps; step++) {8        double progress = (double) step / steps;9        double angle = step * radiansPerStep;10        double x = radius * Math.cos(angle);11        double y = height * progress;12        double z = radius * Math.sin(angle);13        IO.println(String.format("step %d: x = %5.2f, y = %4.2f, z = %5.2f", step, x, y, z));14    }15}
  1. How far along we are, from 0.0 at the bottom to 1.0 at the top. The (double) makes Java divide with decimals instead of whole numbers.
  2. The angle keeps growing, so every step turns further around the circle.
  3. Climbs steadily from 0 to the full height. This one line turns the flat circle into a spiral.

Now move this math into a plugin. Instead of printing, each point becomes a particle. The calls live in a small class called Shapes:

Shapes.javaCompiles on Paper 26.3Compile Lab
Where this file liveseffects-particles-sounds-demosrcmainjavacomexampleeffectsparticlessoundsdemoShapes.java

The package com.example.effectsparticlessoundsdemo is the folder path com/example/effectsparticlessoundsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • effects-particles-sounds-demo/
    • src/main/
      • java/com/example/effectsparticlessoundsdemo/Package com.example.effectsparticlessoundsdemo
        • AuraCommand.javaCommand (BasicCommand)
        • AuraTask.javaHelper class
        • EffectsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • JoinBuffListener.javaListener: reacts to events
        • Shapes.javayou are hereHelper class
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.effectsparticlessoundsdemo;2 3import org.bukkit.Location;4import org.bukkit.Particle;5import org.bukkit.World;6 7public final class Shapes {8 9    private static final double RADIANS_PER_STEP = 0.5;10 11    private Shapes() {12    }13 14    public static void ring(Location center, double radius, int points, double rotation,15                            Particle.DustOptions dust) {16        World world = center.getWorld();17        for (int i = 0; i < points; i++) {18            double angle = rotation + 2 * Math.PI * i / points;19            double x = center.getX() + radius * Math.cos(angle);20            double z = center.getZ() + radius * Math.sin(angle);21            world.spawnParticle(Particle.DUST, x, center.getY() + 0.1, z, 1, dust);22        }23    }24 25    public static void helix(Location base, double radius, double height, int steps, double rotation,26                             Particle particle) {27        World world = base.getWorld();28        for (int step = 0; step <= steps; step++) {29            double progress = (double) step / steps;30            double angle = rotation + step * RADIANS_PER_STEP;31            double x = base.getX() + radius * Math.cos(angle);32            double y = base.getY() + height * progress;33            double z = base.getZ() + radius * Math.sin(angle);34            world.spawnParticle(particle, x, y, z, 1, 0.0, 0.0, 0.0, 0.0);35        }36    }37}
  1. A private constructor means "nobody makes a Shapes object". The class is only a shelf of tools, and its methods are static, so you call them as Shapes.ring(...).
  2. Draws a circle of dust around center. The rotation parameter lets the caller turn the whole ring a little each time, which makes it spin.
  3. The same formula as the plain Java example, plus the rotation.
  4. A hair above the ground, so the dust is not hidden inside the floor.
  5. Spawns one dust particle at the exact point. 1 is the count, and dust is the data.
  6. How far each helix step turns around the circle. A named constant, so nobody wonders what the 0.5 is. Smaller numbers make a loose spring, bigger numbers a tight one.
  7. Draws a spiral that rises height blocks above base.
  8. The climbing line from the plain Java example, now added to the base height.
  9. One particle, no spread and no extra speed, so it stays exactly on the curve.

Sounds, in one minute

Sounds work exactly like particles: a place, a thing to play, and a volume. Titles, action bars, boss bars and sounds explains them in detail. Two lines are enough to remember here:

Two ways to play a sound
1player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.2f);2world.playSound(location, Sound.ENTITY_PLAYER_LEVELUP, SoundCategory.PLAYERS, 1.0f, 1.0f);
  1. Plays the sound to one player only. Volume 1.0 is normal, and pitch 1.2 is a little higher.
  2. Plays at a place in the world, so everyone close enough hears it. The SoundCategory says which volume slider in the settings controls it.

A good habit: pair every important particle effect with a sound. A level-up chime together with green sparkles feels like a reward, and either one alone feels thin.

World effects: explosions, lightning and fireworks

The World can also produce big effects in one call.

Explosions

world.createExplosion(location, power, setFire, breakBlocks) makes an explosion. Power 4 is the size of a TNT block, and power 1 is small. The two true-or-false values decide if the blocks nearby catch fire and if they are destroyed. With both set to false, you get the flash, the sound and the damage to creatures, but the landscape is left intact. That makes it safe for shows and spells.

Lightning: effect or strike?

CallSound and flashDamagesSets fire
world.strikeLightningEffect(location)YesNoNo
world.strikeLightning(location)YesYes, and it turns creepers charged and villagers into witchesYes

For decoration, use strikeLightningEffect. It does what the name says: only the effect.

Fireworks

A firework is an entity (see Entities and mobs) that flies up and explodes. Its look is stored in a FireworkMeta, which you build from FireworkEffect pieces: a shape, colors, an optional fade, a trail and a flicker. You spawn it with the lambda form of spawn, change the meta inside, and set the flight power (how high it flies, 1 to 3 is typical):

ShowCommand.javaCompiles on Paper 26.3Compile Lab
Where this file liveseffects-particles-sounds-worldsrcmainjavacomexampleeffectsparticlessoundsworldShowCommand.java

The package com.example.effectsparticlessoundsworld is the folder path com/example/effectsparticlessoundsworld inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • effects-particles-sounds-world/
    • src/main/
      • java/com/example/effectsparticlessoundsworld/Package com.example.effectsparticlessoundsworld
        • ShowCommand.javayou are hereCommand (BasicCommand)
        • WorldEffectsPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

34private void launchFirework(World world, Location spot) {35    FireworkEffect burst = FireworkEffect.builder()36        .with(FireworkEffect.Type.BALL_LARGE)37        .withColor(Color.AQUA, Color.WHITE)38        .withFade(Color.PURPLE)39        .withTrail()40        .withFlicker()41        .build();42    world.spawn(spot, Firework.class, firework -> {43        FireworkMeta meta = firework.getFireworkMeta();44        meta.addEffect(burst);45        meta.setPower(1);46        firework.setFireworkMeta(meta);47    });48    world.playSound(spot, Sound.ENTITY_PLAYER_LEVELUP, SoundCategory.PLAYERS, 1.0f, 1.0f);49}
  1. Starts describing the look of one explosion. Each line below adds a detail.
  2. The shape. Others are BALL, STAR, BURST and CREEPER.
  3. The main colors. Give several and they mix.
  4. The sparks fade into purple at the end.
  5. Leaves a trail behind each spark.
  6. Makes the sparks twinkle.
  7. The lambda runs on the new firework before it appears, like the mob lambda on the entities page.
  8. Gets a copy of the firework's meta. Changing the copy alone does nothing until you set it back.
  9. A short flight: the firework rises about one second before exploding.
  10. Puts the changed meta back. Forgetting this line is the classic firework bug: a plain, white, boring firework.
  11. A level-up chime to go with it.

The command also holds the other two world effects, in the first part of the same file:

ShowCommand.javaCompiles on Paper 26.3Compile Lab
Where this file liveseffects-particles-sounds-worldsrcmainjavacomexampleeffectsparticlessoundsworldShowCommand.java

The package com.example.effectsparticlessoundsworld is the folder path com/example/effectsparticlessoundsworld inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • effects-particles-sounds-world/
    • src/main/
      • java/com/example/effectsparticlessoundsworld/Package com.example.effectsparticlessoundsworld
        • ShowCommand.javayou are hereCommand (BasicCommand)
        • WorldEffectsPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

26switch (effect) {27    case "firework" -> launchFirework(world, spot);28    case "boom" -> world.createExplosion(spot, 3.0f, false, false);29    case "zap" -> world.strikeLightningEffect(spot);30    default -> player.sendRichMessage("<yellow>Use /show firework, boom or zap.");31}
  1. An explosion with a power of 3 that sets nothing on fire and breaks no blocks. Note the f after the 3.0: Java needs it for a float number.
  2. A harmless bolt, five blocks in front of you.

Example plugin: /aura

Now we combine everything. The demo plugin does two things:

  • /aura turns on a spinning ring of purple dust around your feet and a rising helix of white sparks. Typing it again turns the aura off.
  • When any player joins, they get a short Regeneration and Speed boost, with green sparkles and a chime.
  • effects-particles-sounds-demo/
    • src/
      • main/
        • java/
          • com/example/effectsparticlessoundsdemo/
            • EffectsDemoPlugin.javaRegisters the command and listener, and starts the repeating aura task
            • AuraCommand.java/aura: adds or removes the player from the set of people with an aura
            • AuraTask.javaRuns every 2 ticks and draws the shapes for everyone in that set
            • Shapes.javaThe circle and helix drawing math
            • JoinBuffListener.javaWelcome buff and sparkles when a player joins
        • resources/
          • plugin.ymlPlugin name, version and main class

The design has one important idea. We do not start a new repeating task for every player who types /aura. We start one task when the plugin starts, and it draws for everybody who is currently in a Set of player ids. The command only adds a player to the set or removes them. That is easier to get right, because there is nothing to cancel and nothing to forget.

EffectsDemoPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file liveseffects-particles-sounds-demosrcmainjavacomexampleeffectsparticlessoundsdemoEffectsDemoPlugin.java

The package com.example.effectsparticlessoundsdemo is the folder path com/example/effectsparticlessoundsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • effects-particles-sounds-demo/
    • src/main/
      • java/com/example/effectsparticlessoundsdemo/Package com.example.effectsparticlessoundsdemo
        • AuraCommand.javaCommand (BasicCommand)
        • AuraTask.javaHelper class
        • EffectsDemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
        • JoinBuffListener.javaListener: reacts to events
        • Shapes.javaHelper class
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.effectsparticlessoundsdemo;2 3import io.papermc.paper.command.brigadier.Commands;4import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;5import java.util.HashSet;6import java.util.Set;7import java.util.UUID;8import org.bukkit.plugin.java.JavaPlugin;9 10public final class EffectsDemoPlugin extends JavaPlugin {11 12    private static final long TICKS_BETWEEN_FRAMES = 2L;13 14    private final Set<UUID> auraPlayers = new HashSet<>();15 16    @Override17    public void onEnable() {18        getServer().getPluginManager().registerEvents(new JoinBuffListener(), this);19        getServer().getScheduler().runTaskTimer(this, new AuraTask(auraPlayers), 0L, TICKS_BETWEEN_FRAMES);20        getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {21            Commands commands = event.registrar();22            commands.register("aura", "Turn your particle aura on or off", new AuraCommand(auraPlayers));23        });24    }25}
  1. A set is a group with no duplicates. It holds the unique ids (UUIDs) of players who have the aura on. We store ids and not Player objects, because a player object becomes useless when they log off.
  2. Starts the repeating task: start right away (0L), then repeat every 2 ticks, which gives 10 frames per second. See Timing and tasks.
  3. The command receives the same set, so the command and the task share it.
AuraCommand.javaCompiles on Paper 26.3Compile Lab
Where this file liveseffects-particles-sounds-demosrcmainjavacomexampleeffectsparticlessoundsdemoAuraCommand.java

The package com.example.effectsparticlessoundsdemo is the folder path com/example/effectsparticlessoundsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • effects-particles-sounds-demo/
    • src/main/
      • java/com/example/effectsparticlessoundsdemo/Package com.example.effectsparticlessoundsdemo
        • AuraCommand.javayou are hereCommand (BasicCommand)
        • AuraTask.javaHelper class
        • EffectsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • JoinBuffListener.javaListener: reacts to events
        • Shapes.javaHelper class
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

17@Override18public void execute(CommandSourceStack source, String[] args) {19    if (!(source.getExecutor() instanceof Player player)) {20        source.getSender().sendRichMessage("<red>Only players can have an aura.");21        return;22    }23    boolean wasOn = auraPlayers.remove(player.getUniqueId());24    if (wasOn) {25        player.sendRichMessage("<yellow>Aura off.");26    } else {27        auraPlayers.add(player.getUniqueId());28        player.sendRichMessage("<light_purple>Aura on. Use /aura again to turn it off.");29    }30}
  1. Tries to remove the player. remove answers true if they were in the set, so this one line both removes and tells us whether the aura was on.
  2. If it was on, we just turned it off. Otherwise we add them and turn it on.
AuraTask.javaCompiles on Paper 26.3Compile Lab
Where this file liveseffects-particles-sounds-demosrcmainjavacomexampleeffectsparticlessoundsdemoAuraTask.java

The package com.example.effectsparticlessoundsdemo is the folder path com/example/effectsparticlessoundsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • effects-particles-sounds-demo/
    • src/main/
      • java/com/example/effectsparticlessoundsdemo/Package com.example.effectsparticlessoundsdemo
        • AuraCommand.javaCommand (BasicCommand)
        • AuraTask.javayou are hereHelper class
        • EffectsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • JoinBuffListener.javaListener: reacts to events
        • Shapes.javaHelper class
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

23@Override24public void run() {25    auraPlayers.removeIf(uuid -> Bukkit.getPlayer(uuid) == null);26    rotation += ROTATION_PER_FRAME;27    for (UUID uuid : auraPlayers) {28        Player player = Bukkit.getPlayer(uuid);29        if (player == null) {30            continue;31        }32        Location feet = player.getLocation();33        Shapes.ring(feet, 1.3, 16, rotation, PURPLE);34        Shapes.helix(feet, 0.7, 2.0, 12, rotation, Particle.END_ROD);35    }36}
  1. Cleans out players who logged off since the last frame. Bukkit.getPlayer returns null for someone who is not online.
  2. Turns the shapes a bit more every frame. Frame after frame, a fixed ring looks like it is spinning.
  3. A safety check, in case the player left between the cleanup and this line.
  4. A ring of 16 purple dust particles with a radius of 1.3 blocks around the player's feet.
  5. A spiral of 13 white sparks, 0.7 blocks wide and 2 blocks tall, ending around head height.
JoinBuffListener.javaCompiles on Paper 26.3Compile Lab
Where this file liveseffects-particles-sounds-demosrcmainjavacomexampleeffectsparticlessoundsdemoJoinBuffListener.java

The package com.example.effectsparticlessoundsdemo is the folder path com/example/effectsparticlessoundsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • effects-particles-sounds-demo/
    • src/main/
      • java/com/example/effectsparticlessoundsdemo/Package com.example.effectsparticlessoundsdemo
        • AuraCommand.javaCommand (BasicCommand)
        • AuraTask.javaHelper class
        • EffectsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • JoinBuffListener.javayou are hereListener: reacts to events
        • Shapes.javaHelper class
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

16@EventHandler17public void onJoin(PlayerJoinEvent event) {18    Player player = event.getPlayer();19    player.addPotionEffect(new PotionEffect(PotionEffectType.REGENERATION, TEN_SECONDS, 1));20    player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, TEN_SECONDS * 3, 0));21    player.getWorld().spawnParticle(Particle.HAPPY_VILLAGER,22        player.getLocation().add(0, 1, 0), 20, 0.5, 0.5, 0.5);23    player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.2f);24    player.sendRichMessage("<green>Welcome! You have a short regeneration and speed boost.");25}
  1. Regeneration II for ten seconds: hearts refill fast.
  2. Speed I for thirty seconds.
  3. Green sparkles, around the player's body (one block above the feet).
  4. Plays the chime for this player only.
Aura on. Use /aura again to turn it off.
Welcome! You have a short regeneration and speed boost.

You can download the whole plugin and try it on your test server (see Build, install and test).

Mistakes to avoid

  • Counting the amplifier from 1. Speed II is amplifier 1.
  • Writing seconds where ticks belong. new PotionEffect(SPEED, 30, 1) lasts one and a half seconds. Multiply by 20.
  • Forgetting the data for DUST or BLOCK, or giving data to a particle that does not take any.
  • Forgetting setFireworkMeta. The firework looks plain.
  • Using createExplosion(location, 4) in a plugin that must not destroy builds. The short forms break blocks by default. Pass false, false.
  • Using strikeLightning for decoration. It burns things and hurts players. Use strikeLightningEffect.
  • Starting a new repeating task for every use of a command and never stopping it. Use one task and a set, as the aura does.
  • Drawing thousands of particles per tick. Players' frame rates drop. Keep each effect small.
  • Storing Player objects in a long-lived set. Store the UUID and look the player up when needed.
Try it

A sprint toggle

Write a /sprint command. If the player does not have Speed, it gives them Speed III for 10 seconds and says so. If they already have Speed, it removes it instead.

Hint 1

Ask player.hasPotionEffect(PotionEffectType.SPEED) inside an if, and return after removing the effect.

Hint 2

Speed III means amplifier 2. Ten seconds is 10 * 20 ticks.

Show the solution
SprintCommand.javaCompiles on Paper 26.3Compile Lab
Where this file liveseffects-particles-sounds-exercisesrcmainjavacomexampleeffectsparticlessoundsexerciseSprintCommand.java

The package com.example.effectsparticlessoundsexercise is the folder path com/example/effectsparticlessoundsexercise inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • effects-particles-sounds-exercise/
    • src/main/
      • java/com/example/effectsparticlessoundsexercise/Package com.example.effectsparticlessoundsexercise
        • EffectsExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • KillSparkListener.javaListener: reacts to events
        • SprintCommand.javayou are hereCommand (BasicCommand)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

13@Override14public void execute(CommandSourceStack source, String[] args) {15    if (!(source.getExecutor() instanceof Player player)) {16        source.getSender().sendRichMessage("<red>Only players can sprint.");17        return;18    }19    if (player.hasPotionEffect(PotionEffectType.SPEED)) {20        player.removePotionEffect(PotionEffectType.SPEED);21        player.sendRichMessage("<yellow>Back to normal speed.");22        return;23    }24    player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, TEN_SECONDS, 2));25    player.sendRichMessage("<green>Speed III for 10 seconds!");26}
  1. Stops here after removing the effect. Without it, the next line would add the effect right back.
  2. Amplifier 2 is Speed III.
Try it

Hearts for the winner

When a player kills any mob, spawn eight HEART particles just above where the mob stood. Nothing should happen if the mob died to lava or a fall.

Hint 1

Use EntityDeathEvent. The dead entity has a getKiller() that is null when no player did it.

Hint 2

A living entity knows its height with getHeight(). Add that to the y of its location to start above its head.

Show the solution
KillSparkListener.javaCompiles on Paper 26.3Compile Lab
Where this file liveseffects-particles-sounds-exercisesrcmainjavacomexampleeffectsparticlessoundsexerciseKillSparkListener.java

The package com.example.effectsparticlessoundsexercise is the folder path com/example/effectsparticlessoundsexercise inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • effects-particles-sounds-exercise/
    • src/main/
      • java/com/example/effectsparticlessoundsexercise/Package com.example.effectsparticlessoundsexercise
        • EffectsExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • KillSparkListener.javayou are hereListener: reacts to events
        • SprintCommand.javaCommand (BasicCommand)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

11@EventHandler12public void onDeath(EntityDeathEvent event) {13    LivingEntity victim = event.getEntity();14    if (victim.getKiller() == null) {15        return;16    }17    victim.getWorld().spawnParticle(Particle.HEART,18        victim.getLocation().add(0, victim.getHeight(), 0), 8, 0.4, 0.3, 0.4);19}
  1. No player killer, no hearts.
  2. Moves the particles from the mob's feet to above its head.

Recap

  • A PotionEffect has a type, a duration in ticks (20 per second) and an amplifier that starts at 0. Use addPotionEffect, hasPotionEffect and removePotionEffect; PotionEffect.INFINITE_DURATION never ends.
  • world.spawnParticle(particle, location, count, offsetX, offsetY, offsetZ, extra) draws a cloud. DUST needs DustOptions and BLOCK needs block data. particle.builder() names each setting.
  • Circles use cx + r * cos(angle) and cz + r * sin(angle). Add a rising y to turn a circle into a helix.
  • Pair visuals with a sound for feedback that feels good.
  • Use createExplosion(location, power, false, false) and strikeLightningEffect for harmless shows, and setFireworkMeta to apply a firework's look.
  • For repeating visuals, run one task and keep a set of player ids, instead of one task per player.

Quick quiz

  1. You want Speed II for one minute. Which effect is correct?

  2. Why does world.spawnParticle(Particle.DUST, location, 10) fail?

  3. What is the difference between strikeLightning and strikeLightningEffect?

  4. Your firework always looks plain white. What did you most likely forget?

  5. Why does the aura demo keep one task and a set of ids, and not one task per player?

Next steps