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
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
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.
| Command | Arguments | Description |
|-------------------------------|--------------------------------------|-------------------------------------------------------------------------------------|
| /launchpad add | | Makes the clicked block into a launchpad. |
| /launchpad remove | | Removes the clicked block as a launchpad. |
| /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 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:reload | | Reloads the configuration and launchpads from disk. |
| Command | Arguments | Description |
|-------------------------------|-----------------------------------------------------|----------------------------------------------------------------------------------------------------------|
| /launchpad add | | Makes the clicked block into a launchpad. |
| /launchpad remove | | Removes the clicked block as a launchpad. |
| /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 horizontalVelocity | Decimal number / "null" | Sets the horizontal velocity for the clicked launchpad. 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 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
@ -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.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.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.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. |

View File

@ -8,7 +8,11 @@ import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The command used to modify launchpads
*/
public class LaunchpadCommand implements CommandExecutor {
@Override
@ -27,28 +31,60 @@ public class LaunchpadCommand implements CommandExecutor {
return false;
}
// Make sure
if (action.needsArgument() && (arguments.length < 2 || !action.isValidArgument(arguments[1]))) {
// Make sure arguments are correct
if (arguments.length <= action.neededArguments() || !hasValidArguments(action, arguments)) {
//TODO: Perhaps display the current value instead when missing an argument?
return false;
}
// Register the modification request
ModificationRequest request = null;
switch (action) {
case ADD, REMOVE -> request = new ModificationRequest(action, null);
case VERTICAL_VELOCITY, HORIZONTAL_VELOCITY, FIXED_DIRECTION -> request = new ModificationRequest(action,
arguments[1].equalsIgnoreCase("null") ? null : arguments[1]);
case ADD, REMOVE ->
ModificationRequestHandler.addRequest(player.getUniqueId(), new ModificationRequest(action, null));
case VERTICAL_VELOCITY, HORIZONTAL_VELOCITY, FIXED_DIRECTION -> ModificationRequestHandler.addRequest(
player.getUniqueId(), new ModificationRequest(action, nullArgument(arguments[1])));
case ABORT -> {
// Retrieving modification requests also removes them
ModificationRequestHandler.getRequests(player.getUniqueId());
commandSender.sendMessage("Launchpad modifications cleared");
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");
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) {
// Display available sub-commands
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
ModificationAction action = ModificationAction.getFromCommandName(arguments[0]);
if (action != null && action.needsArgument()) {
return TabCompleteHelper.filterMatchingContains(getTabCompletions(action), arguments[1]);
} else {
if (action == null || action.neededArguments() + 1 < arguments.length) {
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<>();
}
@ -49,7 +52,7 @@ public class LaunchpadTabCompleter implements TabCompleter {
private @NotNull List<String> getTabCompletions(@NotNull ModificationAction action) {
return switch (action) {
case FIXED_DIRECTION -> getBlockFaces();
case HORIZONTAL_VELOCITY, VERTICAL_VELOCITY -> getPositiveDoubles();
case HORIZONTAL_VELOCITY, VERTICAL_VELOCITY, VELOCITIES -> getPositiveDoubles();
default -> new ArrayList<>();
};
}

View File

@ -12,6 +12,7 @@ import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -190,37 +191,66 @@ public class LaunchpadConfiguration {
// Start particle spawning if enabled
if (this.particlesEnabled) {
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());
}
loadLaunchpadParticleConfig(particlesSection);
}
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 {
trailType = Particle.valueOf(particlesSection.getString("trailType"));
} catch (IllegalArgumentException | NullPointerException exception) {
trailType = Particle.EGG_CRACK;
randomTrailWhitelist.add(Particle.valueOf(string));
} catch (IllegalArgumentException exception) {
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, 20, particlesSection.getInt("trailSpawnDelay", 1));
}
trailSpawner = new ParticleTrailSpawner(trailType, randomTrailType, new ArrayList<>(randomTrailWhitelist));
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
*/
REMOVE("remove", false),
REMOVE("remove", 0),
/**
* The action to register a launchpad
*/
ADD("add", false),
ADD("add", 0),
/**
* 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
*/
HORIZONTAL_VELOCITY("horizontalVelocity", true),
HORIZONTAL_VELOCITY("horizontalVelocity", 1),
/**
* The action of setting the fixed direction of a launchpad
*/
FIXED_DIRECTION("fixedDirection", true),
FIXED_DIRECTION("fixedDirection", 1),
/**
* 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 boolean needsArgument;
private final int neededArguments;
/**
* Instantiates a new modification action
*
* @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 commandName <p>The name of the command used to specify this action in command input</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.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() {
return this.needsArgument;
public int neededArguments() {
return this.neededArguments;
}
/**
@ -82,7 +87,8 @@ public enum ModificationAction {
if (argument.equalsIgnoreCase("null")) {
return true;
}
if (this == ModificationAction.HORIZONTAL_VELOCITY || this == ModificationAction.VERTICAL_VELOCITY) {
if (this == ModificationAction.HORIZONTAL_VELOCITY || this == ModificationAction.VERTICAL_VELOCITY ||
this == ModificationAction.VELOCITIES) {
try {
return Double.parseDouble(argument) >= 0;
} catch (NumberFormatException exception) {

View File

@ -7,7 +7,11 @@ 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;
@ -17,15 +21,24 @@ import java.util.UUID;
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 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.randomTrailType = randomTrailType;
this.randomTrailTypes = randomTrailTypes;
}
@Override
@ -43,7 +56,17 @@ public class ParticleTrailSpawner implements Runnable {
if (playerWorld == null) {
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);
@ -56,6 +79,7 @@ public class ParticleTrailSpawner implements Runnable {
*/
public void removeTrail(UUID 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>
*/
public void startTrail(UUID playerId) {
this.playerParticles.put(playerId, randomParticle());
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.
# See https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Particle.html
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
enabled: false
# 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
# Particle configurations per material. So you'd set materialParticles.LIGHT_WEIGHTED_PRESSURE_PLATE.type to set the
# 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