Skip to content

Commit

Permalink
Implement playsound command (#531)
Browse files Browse the repository at this point in the history
* Add the /playsound command

* Add a factory for the SoundCategory enum

* Simplify the unit tests for commands: setidletimeout, setworldspawnpoint, spawnpoint and toggleDownfall
  • Loading branch information
FlorentClarret authored and Momothereal committed Jul 22, 2017
1 parent 123d959 commit b79d4d3
Show file tree
Hide file tree
Showing 10 changed files with 542 additions and 169 deletions.
1 change: 1 addition & 0 deletions src/main/java/net/glowstone/GlowServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,7 @@ private void loadPlugins() {
commandMap.register("minecraft", new SpawnPointCommand());
commandMap.register("minecraft", new ToggleDownfallCommand());
commandMap.register("minecraft", new SetWorldSpawnCommand());
commandMap.register("minecraft", new PlaySoundCommand());

File folder = new File(config.getString(Key.PLUGIN_FOLDER));
if (!folder.isDirectory() && !folder.mkdirs()) {
Expand Down
181 changes: 181 additions & 0 deletions src/main/java/net/glowstone/command/minecraft/PlaySoundCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package net.glowstone.command.minecraft;

import net.glowstone.command.CommandTarget;
import net.glowstone.command.CommandUtils;
import net.glowstone.constants.GlowSound;
import net.glowstone.entity.GlowPlayer;
import net.glowstone.util.SoundUtil;
import org.bukkit.*;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.VanillaCommand;
import org.bukkit.entity.Entity;
import org.bukkit.util.StringUtil;

import java.util.*;
import java.util.stream.Collectors;

public class PlaySoundCommand extends VanillaCommand {

private static final List<String> SOURCES = Arrays.stream(SoundCategory.values()).map(SoundCategory::name).map(String::toLowerCase).collect(Collectors.toList());

private static final Set<String> SOUNDS = GlowSound.getSounds().keySet();

public PlaySoundCommand() {
super("playsound", "Plays a sound.", "/playsound <sound> <source> <player> [x] [y] [z] [volume] [pitch] [minimumVolume]", Collections.emptyList());
setPermission("minecraft.command.playsound");
}

@Override
public boolean execute(CommandSender sender, String label, String[] args) {
if (!testPermission(sender)) return false;

if (args.length < 3 || args.length == 4 || args.length == 5) {
sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
return false;
}

final World world = CommandUtils.getWorld(sender);

if (world == null) {
return false;
}

String stringSound = args[0], stringCategory = args[1], playerPattern = args[2];
final Sound sound = GlowSound.getVanillaSound(stringSound.startsWith("minecraft:") ? stringSound : "minecraft:" + stringSound);
final SoundCategory soundCategory = SoundUtil.buildSoundCategory(stringCategory);
List<GlowPlayer> targets;
boolean relativeLocation = false;
double volume = 1, minimumVolume = 0, pitch = 1;

if (sound == null) {
sender.sendMessage(ChatColor.RED + "'" + stringSound + "' is not a valid sound.");
return false;
}

if (soundCategory == null) {
sender.sendMessage(ChatColor.RED + "'" + stringCategory + "' is not a valid sound category.");
return false;
}

// Manage player(s)
if (playerPattern.startsWith("@") && playerPattern.length() > 1 && CommandUtils.isPhysical(sender)) { // Manage selectors
final Location senderLocation = CommandUtils.getLocation(sender);
final Entity[] entities = new CommandTarget(sender, args[0]).getMatched(senderLocation);
targets = Arrays.stream(entities).filter(GlowPlayer.class::isInstance).map(GlowPlayer.class::cast).collect(Collectors.toList());
} else {
final GlowPlayer player = (GlowPlayer) Bukkit.getPlayerExact(playerPattern);

if (player == null) {
sender.sendMessage(ChatColor.RED + "Player '" + playerPattern + "' cannot be found");
return false;
} else {
targets = Collections.singletonList(player);
}
}

if (args.length >= 9) {
try {
minimumVolume = Double.valueOf(args[8]);

if (minimumVolume < 0 || minimumVolume > 1) {
sender.sendMessage(ChatColor.RED + "Minimum volume value (" + args[8] + ") must be between 0 and 1");
return false;
}
} catch (final NumberFormatException n) {
sender.sendMessage(ChatColor.RED + "'" + args[8] + "' is not a valid number");
return false;
}
}

if (args.length >= 8) {
try {
pitch = Double.valueOf(args[7]);

if (pitch < 0 || pitch > 2) {
sender.sendMessage(ChatColor.RED + "Pitch value (" + args[7] + ") must be between 0 and 2");
return false;
} else if (pitch < 0.5) {
pitch = 0.5;
}

} catch (final NumberFormatException n) {
sender.sendMessage(ChatColor.RED + "'" + args[7] + "' is not a valid number");
return false;
}
}

if (args.length >= 7) {
try {
volume = Double.valueOf(args[6]);
} catch (final NumberFormatException n) {
sender.sendMessage(ChatColor.RED + "'" + args[6] + "' is not a valid number");
return false;
}
}

if (args.length >= 6) {
relativeLocation = args[3].startsWith("~") || args[4].startsWith("~") || args[5].startsWith("~");
}

for (final GlowPlayer target : targets) {
Location soundLocation, targetLocation = target.getLocation();
double targetVolume = volume;

try {
if (relativeLocation) {
soundLocation = CommandUtils.getLocation(targetLocation, args[3], args[4], args[5]);
} else if (args.length >= 6) {
soundLocation = CommandUtils.getLocation(new Location(world, 0, 0, 0), args[3], args[4], args[5]);
} else {
soundLocation = targetLocation;
}
} catch (final NumberFormatException n) {
sender.sendMessage(ChatColor.RED + "The position (" + args[3] + "," + args[4] + "," + args[5] + ") is invalid");
return false;
}

// If the target is outside the normal audible sphere
if (targetLocation.distanceSquared(soundLocation) > Math.pow(volume, 2)) {
if (minimumVolume <= 0) {
sender.sendMessage(ChatColor.RED + target.getName() + " is too far away to hear the sound");
return false;
} else {
final double deltaX = soundLocation.getX() - targetLocation.getX(),
deltaY = soundLocation.getX() - targetLocation.getY(),
deltaZ = soundLocation.getX() - targetLocation.getZ();
final double delta = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2) + Math.pow(deltaZ, 2));

soundLocation = targetLocation;
soundLocation.add(deltaX / delta, deltaY / delta, deltaZ / delta);
targetVolume = minimumVolume;
}
}

target.playSound(soundLocation, sound, soundCategory, (float) targetVolume, (float) pitch);
}

return true;
}

@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
if (args == null) {
return Collections.emptyList();
} else if (args.length == 1) {
String sound = args[0];

if (!sound.startsWith("minecraft:")) {
final int colonIndex = sound.indexOf(':');
sound = "minecraft:" + sound.substring(colonIndex == -1 ? 0 : (colonIndex + 1));
}

return StringUtil.copyPartialMatches(sound, SOUNDS, new ArrayList(SOUNDS.size()));
} else if (args.length == 2) {
return StringUtil.copyPartialMatches(args[1], SOURCES, new ArrayList(SOURCES.size()));
} else if (args.length == 3) {
return super.tabComplete(sender, alias, args);
} else {
return Collections.emptyList();
}
}
}
11 changes: 8 additions & 3 deletions src/main/java/net/glowstone/constants/GlowSound.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.glowstone.constants;

import com.google.common.collect.ImmutableMap;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;

Expand All @@ -8,7 +9,7 @@

public class GlowSound {

private static Map<String, SoundCategory> sounds = new HashMap<>();
private static final Map<String, SoundCategory> SOUNDS = new HashMap<>();

static {
// register vanilla sounds
Expand All @@ -20,11 +21,11 @@ public class GlowSound {
}

public static void reg(String id, SoundCategory category) {
sounds.put(id, category);
SOUNDS.put(id, category);
}

public static SoundCategory getSoundCategory(String id) {
return sounds.get(id);
return SOUNDS.get(id);
}

public static String getVanillaId(Sound sound) {
Expand All @@ -45,4 +46,8 @@ public static Sound getVanillaSound(String id) {
public static SoundCategory getCategory(int category) {
return SoundCategory.values()[category];
}

public static Map<String, SoundCategory> getSounds() {
return (Map) ImmutableMap.builder().putAll(SOUNDS).build();
}
}
20 changes: 20 additions & 0 deletions src/main/java/net/glowstone/util/SoundUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import net.glowstone.entity.GlowPlayer;
import org.bukkit.Location;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
Expand Down Expand Up @@ -36,4 +37,23 @@ public static float randomReal(float range) {
ThreadLocalRandom rand = ThreadLocalRandom.current();
return (rand.nextFloat() - rand.nextFloat()) * range;
}

/**
* Convert a string to a SoundCategory. The comparison is done on the name and is not case-sensitive.
* @param category The string name of the category
* @return The matching SoundCategory, null if none.
*/
public static SoundCategory buildSoundCategory(final String category) {
if (category == null) {
return null;
}

for (final SoundCategory soundCategory : SoundCategory.values()) {
if (category.equalsIgnoreCase(soundCategory.name())) {
return soundCategory;
}
}

return null;
}
}
Loading

0 comments on commit b79d4d3

Please sign in to comment.