Adds several improvements

Adds a combined command for setting both the horizontal and vertical velocities of a launchpad.
Adds support for multiple arguments in /launchpad sub-commands
Adds support for randomized player trails when launching from a launchpad
This commit is contained in:
Kristian Knarvik 2023-07-06 00:50:45 +02:00
parent 19477a31ae
commit 8a8daa5f6e
7 changed files with 238 additions and 67 deletions

View File

@ -7,20 +7,28 @@ have more accurate hit-detection as that's built into Minecraft.
Launchpads will prioritize values specifically set for that launchpad. If not set, they will look for material-specific Launchpads will prioritize values specifically set for that launchpad. If not set, they will look for material-specific
values. If not set, they will look for the generic velocities in the config. values. If not set, they will look for the generic velocities in the config.
## Known problems
#### Jumping on a pressure plate causes a `Player moved too quickly` message in the console, and the player is weirdly glitched for a moment
This happens because the server thinks the player moved faster than it should, and forcefully prevents the player from
moving. Increasing `moved-too-quickly-multiplier` in `spigot.yml` may fix this issue.
## Commands ## Commands
Note that changing a property for a block which isn't currently a launchpad will turn the block into a launchpad. Note that changing a property for a block which isn't currently a launchpad will turn the block into a launchpad.
If you alter several launchpad values in succession, they'll all be applied to the next block you right-click. If you alter several launchpad values in succession, they'll all be applied to the next block you right-click.
| Command | Arguments | Description | | Command | Arguments | Description |
|-------------------------------|--------------------------------------|-------------------------------------------------------------------------------------| |-------------------------------|-----------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| /launchpad add | | Makes the clicked block into a launchpad. | | /launchpad add | | Makes the clicked block into a launchpad. |
| /launchpad remove | | Removes the clicked block as a launchpad. | | /launchpad remove | | Removes the clicked block as a launchpad. |
| /launchpad abort | | Clears any unprocessed launchpad modifications. | | /launchpad abort | | Clears any unprocessed launchpad modifications. |
| /launchpad verticalVelocity | Decimal number / "null" | Sets the vertical velocity for the clicked launchpad. Use "null" to unset. | | /launchpad verticalVelocity | Decimal number / "null" | Sets the vertical velocity for the clicked launchpad. Use "null" to unset. |
| /launchpad horizontalVelocity | Decimal number / "null" | Sets the horizontal velocity for the clicked launchpad. Use "null" to unset. | | /launchpad horizontalVelocity | Decimal number / "null" | Sets the horizontal velocity for the clicked launchpad. Use "null" to unset. |
| /launchpad fixedDirection | NORTH / SOUTH / EAST / WEST / "null" | Sets a fixed direction the launchpad will launch every player. Use "null" to unset. | | /launchpad velocities | <Decimal Number / "null"> <Decimal Number / "null"> | Sets the horizontal and vertical velocities at once. The first argument is for the horizontal velocity. |
| /launchpad:reload | | Reloads the configuration and launchpads from disk. | | /launchpad fixedDirection | NORTH / SOUTH / EAST / WEST / "null" | Sets a fixed direction the launchpad will launch every player. Use "null" to unset. |
| /launchpad:reload | | Reloads the configuration and launchpads from disk. |
## Permissions ## Permissions
@ -43,6 +51,8 @@ If you alter several launchpad values in succession, they'll all be applied to t
| launchpad.particles.trailsEnabled | True / False | Whether to enable particle trails behind players | | launchpad.particles.trailsEnabled | True / False | Whether to enable particle trails behind players |
| launchpad.particles.trailSpawnDelay | Positive integer | The amount of ticks (1 second = 20 ticks) between each time the particle(s) of a trail should be spawned. | | launchpad.particles.trailSpawnDelay | Positive integer | The amount of ticks (1 second = 20 ticks) between each time the particle(s) of a trail should be spawned. |
| launchpad.particles.trailType | [Particle](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html) | The type of trail to spawn behind launched players. | | launchpad.particles.trailType | [Particle](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html) | The type of trail to spawn behind launched players. |
| launchpad.particles.randomTrailType | True / False | Whether to use a random value from randomTrailWhitelist as the trail on each launch. |
| launchpad.particles.randomTrailWhitelist | List | A list of all [particles](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html) selectable for random trails. |
| launchpad.particles.enabled | True / False | Whether to display some kind of particle effect above manually added launchpads. | | launchpad.particles.enabled | True / False | Whether to display some kind of particle effect above manually added launchpads. |
| launchpad.particles.spawnDelay | Positive integer | The amount of ticks (1 second = 20 ticks) between each time the particle(s) should be spawned again. Depending on the particle, higher values will make the particle(s) completely disappear and reappear. | | launchpad.particles.spawnDelay | Positive integer | The amount of ticks (1 second = 20 ticks) between each time the particle(s) should be spawned again. Depending on the particle, higher values will make the particle(s) completely disappear and reappear. |
| launchpad.particles.particle.mode | SINGLE / SQUARE / PYRAMID / SPHERE / CIRCLE / CUBE | The mode used for drawing particles. SINGLE directly spawns the particle(s) in one spot above the launchpad. The other ones spawn particles a bunch of times in a pattern. | | launchpad.particles.particle.mode | SINGLE / SQUARE / PYRAMID / SPHERE / CIRCLE / CUBE | The mode used for drawing particles. SINGLE directly spawns the particle(s) in one spot above the launchpad. The other ones spawn particles a bunch of times in a pattern. |

View File

@ -8,7 +8,11 @@ import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The command used to modify launchpads
*/
public class LaunchpadCommand implements CommandExecutor { public class LaunchpadCommand implements CommandExecutor {
@Override @Override
@ -27,28 +31,60 @@ public class LaunchpadCommand implements CommandExecutor {
return false; return false;
} }
// Make sure // Make sure arguments are correct
if (action.needsArgument() && (arguments.length < 2 || !action.isValidArgument(arguments[1]))) { if (arguments.length <= action.neededArguments() || !hasValidArguments(action, arguments)) {
//TODO: Perhaps display the current value instead when missing an argument? //TODO: Perhaps display the current value instead when missing an argument?
return false; return false;
} }
// Register the modification request // Register the modification request
ModificationRequest request = null;
switch (action) { switch (action) {
case ADD, REMOVE -> request = new ModificationRequest(action, null); case ADD, REMOVE ->
case VERTICAL_VELOCITY, HORIZONTAL_VELOCITY, FIXED_DIRECTION -> request = new ModificationRequest(action, ModificationRequestHandler.addRequest(player.getUniqueId(), new ModificationRequest(action, null));
arguments[1].equalsIgnoreCase("null") ? null : arguments[1]); case VERTICAL_VELOCITY, HORIZONTAL_VELOCITY, FIXED_DIRECTION -> ModificationRequestHandler.addRequest(
player.getUniqueId(), new ModificationRequest(action, nullArgument(arguments[1])));
case ABORT -> { case ABORT -> {
// Retrieving modification requests also removes them // Retrieving modification requests also removes them
ModificationRequestHandler.getRequests(player.getUniqueId()); ModificationRequestHandler.getRequests(player.getUniqueId());
commandSender.sendMessage("Launchpad modifications cleared"); commandSender.sendMessage("Launchpad modifications cleared");
return true; return true;
} }
case VELOCITIES -> {
ModificationRequestHandler.addRequest(player.getUniqueId(), new ModificationRequest(
ModificationAction.HORIZONTAL_VELOCITY, nullArgument(arguments[1])));
ModificationRequestHandler.addRequest(player.getUniqueId(), new ModificationRequest(
ModificationAction.VERTICAL_VELOCITY, nullArgument(arguments[2])));
}
} }
ModificationRequestHandler.addRequest(player.getUniqueId(), request);
commandSender.sendMessage("Right-click the block to modify launchpad properties for"); commandSender.sendMessage("Right-click the block to modify launchpad properties for");
return true; return true;
} }
/**
* Checks whether the given action has valid arguments
*
* @param action <p>The action to check</p>
* @param arguments <p>The arguments provided</p>
* @return <p>True if the arguments are valid for the action</p>
*/
private boolean hasValidArguments(@NotNull ModificationAction action, @NotNull String[] arguments) {
for (int i = 0; i < action.neededArguments(); i++) {
if (!action.isValidArgument(arguments[i + 1])) {
return false;
}
}
return true;
}
/**
* Converts the string "null" to a real null
*
* @param argument <p>The argument to null</p>
* @return <p>The nullified argument</p>
*/
private @Nullable String nullArgument(@NotNull String argument) {
return argument.equalsIgnoreCase("null") ? null : argument;
}
} }

View File

@ -28,14 +28,17 @@ public class LaunchpadTabCompleter implements TabCompleter {
if (arguments.length == 1) { if (arguments.length == 1) {
// Display available sub-commands // Display available sub-commands
return TabCompleteHelper.filterMatchingContains(getModificationActions(), arguments[0]); return TabCompleteHelper.filterMatchingContains(getModificationActions(), arguments[0]);
} else if (arguments.length == 2) { } else {
// If given a valid modification action, and an argument is expected, display possible values // If given a valid modification action, and an argument is expected, display possible values
ModificationAction action = ModificationAction.getFromCommandName(arguments[0]); ModificationAction action = ModificationAction.getFromCommandName(arguments[0]);
if (action != null && action.needsArgument()) { if (action == null || action.neededArguments() + 1 < arguments.length) {
return TabCompleteHelper.filterMatchingContains(getTabCompletions(action), arguments[1]);
} else {
return new ArrayList<>(); return new ArrayList<>();
} }
if (arguments.length == 2) {
return TabCompleteHelper.filterMatchingContains(getTabCompletions(action), arguments[1]);
} else if (arguments.length == 3) {
return TabCompleteHelper.filterMatchingContains(getTabCompletions(action), arguments[2]);
}
} }
return new ArrayList<>(); return new ArrayList<>();
} }
@ -49,7 +52,7 @@ public class LaunchpadTabCompleter implements TabCompleter {
private @NotNull List<String> getTabCompletions(@NotNull ModificationAction action) { private @NotNull List<String> getTabCompletions(@NotNull ModificationAction action) {
return switch (action) { return switch (action) {
case FIXED_DIRECTION -> getBlockFaces(); case FIXED_DIRECTION -> getBlockFaces();
case HORIZONTAL_VELOCITY, VERTICAL_VELOCITY -> getPositiveDoubles(); case HORIZONTAL_VELOCITY, VERTICAL_VELOCITY, VELOCITIES -> getPositiveDoubles();
default -> new ArrayList<>(); default -> new ArrayList<>();
}; };
} }

View File

@ -12,6 +12,7 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -190,37 +191,66 @@ public class LaunchpadConfiguration {
// Start particle spawning if enabled // Start particle spawning if enabled
if (this.particlesEnabled) { if (this.particlesEnabled) {
ConfigurationSection particleSection = particlesSection.getConfigurationSection("particle"); loadLaunchpadParticleConfig(particlesSection);
LaunchpadParticleConfig particleConfig = null;
if (particleSection != null) {
particleConfig = new LaunchpadParticleConfig(particleSection);
}
// Load any per-material configuration options
Map<Material, LaunchpadParticleConfig> materialConfigs;
ConfigurationSection perMaterialSection = particlesSection.getConfigurationSection("materialParticles");
if (perMaterialSection != null) {
materialConfigs = loadMaterialParticleConfigs(perMaterialSection);
} else {
materialConfigs = new HashMap<>();
}
if (particleConfig != null) {
particleTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Launchpad.getInstance(),
new ParticleSpawner(particleConfig, materialConfigs), 20, particleConfig.getSpawnDelay());
}
} }
if (this.trailsEnabled) { if (this.trailsEnabled) {
Particle trailType; loadTrailParticleConfig(particlesSection);
}
}
/**
* Loads all trail particle-related configuration values
*
* @param particlesSection <p>The configuration section containing particle options</p>
*/
private void loadTrailParticleConfig(ConfigurationSection particlesSection) {
Particle trailType;
try {
trailType = Particle.valueOf(particlesSection.getString("trailType"));
} catch (IllegalArgumentException | NullPointerException exception) {
trailType = Particle.EGG_CRACK;
}
boolean randomTrailType = particlesSection.getBoolean("randomTrailType", false);
List<String> randomTrailList = particlesSection.getStringList("randomTrailWhitelist");
Set<Particle> randomTrailWhitelist = new HashSet<>();
for (String string : randomTrailList) {
try { try {
trailType = Particle.valueOf(particlesSection.getString("trailType")); randomTrailWhitelist.add(Particle.valueOf(string));
} catch (IllegalArgumentException | NullPointerException exception) { } catch (IllegalArgumentException exception) {
trailType = Particle.EGG_CRACK; Launchpad.log(Level.WARNING, "Unable to parse particle " + string +
" from the random trail type whitelist.");
} }
trailSpawner = new ParticleTrailSpawner(trailType); }
particleTrailTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Launchpad.getInstance(), trailSpawner = new ParticleTrailSpawner(trailType, randomTrailType, new ArrayList<>(randomTrailWhitelist));
trailSpawner, 20, particlesSection.getInt("trailSpawnDelay", 1)); particleTrailTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Launchpad.getInstance(),
trailSpawner, 20, particlesSection.getInt("trailSpawnDelay", 1));
}
/**
* Loads all launchpad particle-related configuration values
*
* @param particlesSection <p>The configuration section containing particle options</p>
*/
private void loadLaunchpadParticleConfig(ConfigurationSection particlesSection) {
ConfigurationSection particleSection = particlesSection.getConfigurationSection("particle");
LaunchpadParticleConfig particleConfig = null;
if (particleSection != null) {
particleConfig = new LaunchpadParticleConfig(particleSection);
}
// Load any per-material configuration options
Map<Material, LaunchpadParticleConfig> materialConfigs;
ConfigurationSection perMaterialSection = particlesSection.getConfigurationSection("materialParticles");
if (perMaterialSection != null) {
materialConfigs = loadMaterialParticleConfigs(perMaterialSection);
} else {
materialConfigs = new HashMap<>();
}
if (particleConfig != null) {
particleTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask(Launchpad.getInstance(),
new ParticleSpawner(particleConfig, materialConfigs), 20, particleConfig.getSpawnDelay());
} }
} }

View File

@ -12,46 +12,51 @@ public enum ModificationAction {
/** /**
* The action to remove a registered launchpad * The action to remove a registered launchpad
*/ */
REMOVE("remove", false), REMOVE("remove", 0),
/** /**
* The action to register a launchpad * The action to register a launchpad
*/ */
ADD("add", false), ADD("add", 0),
/** /**
* The action to modify the vertical velocity of a launchpad * The action to modify the vertical velocity of a launchpad
*/ */
VERTICAL_VELOCITY("verticalVelocity", true), VERTICAL_VELOCITY("verticalVelocity", 1),
/** /**
* The action to modify the horizontal velocity of a launchpad * The action to modify the horizontal velocity of a launchpad
*/ */
HORIZONTAL_VELOCITY("horizontalVelocity", true), HORIZONTAL_VELOCITY("horizontalVelocity", 1),
/** /**
* The action of setting the fixed direction of a launchpad * The action of setting the fixed direction of a launchpad
*/ */
FIXED_DIRECTION("fixedDirection", true), FIXED_DIRECTION("fixedDirection", 1),
/** /**
* The action of aborting previous actions * The action of aborting previous actions
*/ */
ABORT("abort", false), ABORT("abort", 0),
/**
* The action of setting both velocities at once
*/
VELOCITIES("velocities", 2),
; ;
private final @NotNull String commandName; private final @NotNull String commandName;
private final boolean needsArgument; private final int neededArguments;
/** /**
* Instantiates a new modification action * Instantiates a new modification action
* *
* @param commandName <p>The name of the command used to specify this action in command input</p> * @param commandName <p>The name of the command used to specify this action in command input</p>
* @param needsArgument <p>Whether the modification action requires an argument in order to be valid</p> * @param neededArguments <p>The number of arguments required for this modification action</p>
*/ */
ModificationAction(@NotNull String commandName, boolean needsArgument) { ModificationAction(@NotNull String commandName, int neededArguments) {
this.commandName = commandName; this.commandName = commandName;
this.needsArgument = needsArgument; this.neededArguments = neededArguments;
} }
/** /**
@ -64,12 +69,12 @@ public enum ModificationAction {
} }
/** /**
* Gets whether this modification action requires an argument * The amount of arguments required by this modification action
* *
* @return <p>True if this action requires an argument</p> * @return <p>The number of arguments required</p>
*/ */
public boolean needsArgument() { public int neededArguments() {
return this.needsArgument; return this.neededArguments;
} }
/** /**
@ -82,7 +87,8 @@ public enum ModificationAction {
if (argument.equalsIgnoreCase("null")) { if (argument.equalsIgnoreCase("null")) {
return true; return true;
} }
if (this == ModificationAction.HORIZONTAL_VELOCITY || this == ModificationAction.VERTICAL_VELOCITY) { if (this == ModificationAction.HORIZONTAL_VELOCITY || this == ModificationAction.VERTICAL_VELOCITY ||
this == ModificationAction.VELOCITIES) {
try { try {
return Double.parseDouble(argument) >= 0; return Double.parseDouble(argument) >= 0;
} catch (NumberFormatException exception) { } catch (NumberFormatException exception) {

View File

@ -7,7 +7,11 @@ import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -17,15 +21,24 @@ import java.util.UUID;
public class ParticleTrailSpawner implements Runnable { public class ParticleTrailSpawner implements Runnable {
private final Set<UUID> playersWithTrails = new HashSet<>(); 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 Particle particle;
private final boolean randomTrailType;
private final List<Particle> randomTrailTypes;
/** /**
* Instantiates a new particle trail spawner * Instantiates a new particle trail spawner
* *
* @param particle <p>The type of particle used for the trail</p> * @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) { public ParticleTrailSpawner(@NotNull Particle particle, boolean randomTrailType,
@NotNull List<Particle> randomTrailTypes) {
this.particle = particle; this.particle = particle;
this.randomTrailType = randomTrailType;
this.randomTrailTypes = randomTrailTypes;
} }
@Override @Override
@ -43,7 +56,17 @@ public class ParticleTrailSpawner implements Runnable {
if (playerWorld == null) { if (playerWorld == null) {
continue; continue;
} }
playerWorld.spawnParticle(this.particle, playerLocation, 1);
Particle spawnParticle;
if (randomTrailType) {
spawnParticle = playerParticles.get(playerId);
if (spawnParticle == null) {
spawnParticle = this.particle;
}
} else {
spawnParticle = this.particle;
}
playerWorld.spawnParticle(spawnParticle, playerLocation, 0, 0, 0, 0, 0);
} }
playersWithTrails.removeAll(offlinePlayers); playersWithTrails.removeAll(offlinePlayers);
@ -56,6 +79,7 @@ public class ParticleTrailSpawner implements Runnable {
*/ */
public void removeTrail(UUID playerId) { public void removeTrail(UUID playerId) {
this.playersWithTrails.remove(playerId); this.playersWithTrails.remove(playerId);
this.playerParticles.remove(playerId);
} }
/** /**
@ -64,7 +88,21 @@ public class ParticleTrailSpawner implements Runnable {
* @param playerId <p>The id of the player to add the trail to</p> * @param playerId <p>The id of the player to add the trail to</p>
*/ */
public void startTrail(UUID playerId) { public void startTrail(UUID playerId) {
this.playerParticles.put(playerId, randomParticle());
this.playersWithTrails.add(playerId); this.playersWithTrails.add(playerId);
} }
/**
* Gets a random particle
*
* @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;
}
} }

View File

@ -28,6 +28,8 @@ launchpad:
# The type of trail to spawn behind launched players. # The type of trail to spawn behind launched players.
# See https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html # See https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html
trailType: EGG_CRACK trailType: EGG_CRACK
# Whether to use a random trail type each time a player is launched instead of the type specified in "trailType".
randomTrailType: false
# Whether to enable particles above launchpads # Whether to enable particles above launchpads
enabled: false enabled: false
# The amount of ticks (1 second = 20 ticks) between each time the particle(s) should be spawned again. Depending on # The amount of ticks (1 second = 20 ticks) between each time the particle(s) should be spawned again. Depending on
@ -57,4 +59,50 @@ launchpad:
extra: 0 extra: 0
# Particle configurations per material. So you'd set materialParticles.LIGHT_WEIGHTED_PRESSURE_PLATE.type to set the # Particle configurations per material. So you'd set materialParticles.LIGHT_WEIGHTED_PRESSURE_PLATE.type to set the
# particle type for LIGHT_WEIGHTED_PRESSURE_PLATE. # particle type for LIGHT_WEIGHTED_PRESSURE_PLATE.
materialParticles: [ ] materialParticles: [ ]
# A whitelist for all particles usable as random trail particles.
randomTrailWhitelist:
- SPELL_WITCH
- SOUL
- DRIP_WATER
- DRIP_LAVA
- FALLING_NECTAR
- CHERRY_LEAVES
- SMALL_FLAME
- DRIPPING_HONEY
- NAUTILUS
- FIREWORKS_SPARK
- VILLAGER_ANGRY
- WAX_ON
- GLOW
- DAMAGE_INDICATOR
- SNOWFLAKE
- WAX_OFF
- SPELL
- SPELL_INSTANT
- SONIC_BOOM
- DRAGON_BREATH
- SCULK_SOUL
- DRIPPING_OBSIDIAN_TEAR
- FALLING_OBSIDIAN_TEAR
- LANDING_OBSIDIAN_TEAR
- EGG_CRACK
- ENCHANTMENT_TABLE
- CLOUD
- END_ROD
- EXPLOSION_NORMAL
- SMOKE_LARGE
- VILLAGER_HAPPY
- FLAME
- HEART
- TOTEM
- SNEEZE
- CAMPFIRE_COSY_SMOKE
- FALLING_HONEY
- LANDING_HONEY
- SOUL_FIRE_FLAME
- REVERSE_PORTAL
- FALLING_SPORE_BLOSSOM
- DRIPPING_DRIPSTONE_LAVA
- DRIPPING_DRIPSTONE_WATER
- SCRAPE