diff --git a/README.MD b/README.MD index a9be3ae..fe8b3fc 100644 --- a/README.MD +++ b/README.MD @@ -25,4 +25,46 @@ There are limitations connected to directly playing media using playSound: - Songs are really only identified by the category and the song id. Stopping the song for one minstrel will also stop the song if played by any other minstrels at the same time. As songs are only stopped if a song has been played for a player outside the global cycle, it shouldn't be too much of a problem, but if several minstrels play the same song, - there may be silent minstrels until the next song plays. \ No newline at end of file + there may be silent minstrels until the next song plays. + +## Commands + +| Command | Arguments | Description | +| --- | --- | --- | +| /minstrel addsong | | Adds the specified song to the selected minstrel's playlist | +| /minstrel removesong | | Removes the song at the given index from the selected minstrel's playlist | +| /minstrel listsongs | none | Lists all songs in the selected NPC's playlist in the format CATEGORY:identifier:duration | +| /minstrel volume | \[force update (true/false)] | Displays or sets the volume of the selected minstrel | +| /minstrel pitch | \[force update (true/false)] | Displays or sets the pitch of the selected minstrel | + +### /minstrel addsong + +- sound category - The category to use when playing the song. This is used when playing the sound to decide which volume + slider can be used to alter the volume. RECORDS is recommended for minstrels. You may also specify "null" as the sound + category. It will then be played without a specific category being specified. +- sound identifier - The string used to identify the sound to play. Example: "minecraft:music_disc.cat" identifies the + cat music disc. You can also specify music in a resource pack, though nothing can be heard for players without the + resource pack. +- duration - The duration of the track, in seconds. It's important that this is exact, and not too short, as it's used + to decide when to start playing the next track in the playlist. If it's too short, several songs will end up playing + at once! Setting it higher than the actual duration can be used to add a pause before the next song is played. + +Example: `/minstrel addsong RECORDS minecraft:music_disc.cat 185` would be used to add the CAT music disc to a playlist. + +### /minstrel removesong + +- index - The 0-based index of the song's position in the playlist. Use `/minstrel listsongs` to see the current + playlist. + +Example: `/minstrel removesong 0` removes the first song in the playlist. + +### /minstrel volume + +- new volume - The new volume of the minstrel. Set to 1 for full volume, 0.5 for half volume, etc. If set above 1, the + minstrel can be heard from farther away, while the actual volume will be the same as for 1.0 when close to the NPC. +- force update - Whether to forcefully stop and start the minstrel's playlist to make the change happen instantly. + +### /minstrel pitch + +- new pitch - The new pitch used by the minstrel. Set to 1 for normal. +- force update - Whether to forcefully stop and start the minstrel's playlist to make the change happen instantly. \ No newline at end of file diff --git a/src/main/java/net/knarcraft/minstrel/command/MinstrelCommand.java b/src/main/java/net/knarcraft/minstrel/command/MinstrelCommand.java index 939497d..54df7e2 100644 --- a/src/main/java/net/knarcraft/minstrel/command/MinstrelCommand.java +++ b/src/main/java/net/knarcraft/minstrel/command/MinstrelCommand.java @@ -32,11 +32,12 @@ public class MinstrelCommand implements CommandExecutor { case "listsongs": return new ListSongsCommand(minstrelTrait).onCommand(sender, command, label, args); case "pitch": - return updatePitch(minstrelTrait, args.length > 1 ? args[1] : null, sender); + return updatePitch(minstrelTrait, args.length > 1 ? args[1] : null, args.length > 2 && + Boolean.parseBoolean(args[2]), sender); case "volume": - return updateVolume(minstrelTrait, args.length > 1 ? args[1] : null, sender); + return updateVolume(minstrelTrait, args.length > 1 ? args[1] : null, args.length > 2 && + Boolean.parseBoolean(args[2]), sender); } - //TODO: Add another argument for volume and pitch for whether to immediately change the volume by restarting the playlist /* Sub-commands: AddSong category identifier duration (remember to run play again) @@ -63,7 +64,7 @@ public class MinstrelCommand implements CommandExecutor { * @param sender

The sender to send error/success messages to

* @return

True if the pitch was successfully updated

*/ - private boolean updatePitch(MinstrelTrait minstrelTrait, String newPitch, CommandSender sender) { + private boolean updatePitch(MinstrelTrait minstrelTrait, String newPitch, boolean forceRefresh, CommandSender sender) { if (newPitch == null) { sender.sendMessage("Current pitch: " + minstrelTrait.getPitch()); return true; @@ -75,6 +76,10 @@ public class MinstrelCommand implements CommandExecutor { sender.sendMessage("The pitch cannot be negative"); } else { minstrelTrait.setPitch(pitch); + if (forceRefresh) { + minstrelTrait.getPlaylist().stop(); + minstrelTrait.getPlaylist().play(minstrelTrait); + } } } catch (NumberFormatException exception) { sender.sendMessage("The given pitch is not a number!"); @@ -92,7 +97,7 @@ public class MinstrelCommand implements CommandExecutor { * @param sender

The sender to send error/success messages to

* @return

True if the volume was successfully updated

*/ - private boolean updateVolume(MinstrelTrait minstrelTrait, String newVolume, CommandSender sender) { + private boolean updateVolume(MinstrelTrait minstrelTrait, String newVolume, boolean forceRefresh, CommandSender sender) { if (newVolume == null) { sender.sendMessage("Current volume: " + minstrelTrait.getVolume()); return true; @@ -104,6 +109,10 @@ public class MinstrelCommand implements CommandExecutor { sender.sendMessage("The volume must be greater than 0"); } else { minstrelTrait.setVolume(volume); + if (forceRefresh) { + minstrelTrait.getPlaylist().stop(); + minstrelTrait.getPlaylist().play(minstrelTrait); + } } } catch (NumberFormatException exception) { sender.sendMessage("The given volume is not a number!"); diff --git a/src/main/java/net/knarcraft/minstrel/command/MinstrelTabCompleter.java b/src/main/java/net/knarcraft/minstrel/command/MinstrelTabCompleter.java index 0db2839..aca2539 100644 --- a/src/main/java/net/knarcraft/minstrel/command/MinstrelTabCompleter.java +++ b/src/main/java/net/knarcraft/minstrel/command/MinstrelTabCompleter.java @@ -1,5 +1,8 @@ package net.knarcraft.minstrel.command; +import net.citizensnpcs.api.CitizensAPI; +import net.citizensnpcs.api.npc.NPC; +import net.knarcraft.minstrel.trait.MinstrelTrait; import org.bukkit.SoundCategory; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -10,10 +13,16 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; +import static net.knarcraft.minstrel.util.TabCompletionHelper.filterMatchingContains; + public class MinstrelTabCompleter implements TabCompleter { private final List baseCommands; private final List soundCategories; + private final List exampleLengths; + private final List empty = new ArrayList<>(); + private final List exampleFloats; + private final List booleans; public MinstrelTabCompleter() { baseCommands = new ArrayList<>(); @@ -26,27 +35,91 @@ public class MinstrelTabCompleter implements TabCompleter { for (SoundCategory category : SoundCategory.values()) { soundCategories.add(category.name()); } + exampleLengths = new ArrayList<>(); + exampleLengths.add("60"); + exampleLengths.add("90"); + exampleLengths.add("120"); + exampleFloats = new ArrayList<>(); + exampleFloats.add("0.5"); + exampleFloats.add("1"); + exampleFloats.add("1.5"); + booleans = new ArrayList<>(); + booleans.add("true"); + booleans.add("false"); } @Nullable @Override public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 2) { - return baseCommands; + NPC npc = CitizensAPI.getDefaultNPCSelector().getSelected(sender); + if (npc == null || !npc.hasTrait(MinstrelTrait.class)) { + return empty; } + MinstrelTrait minstrelTrait = npc.getTraitNullable(MinstrelTrait.class); + + if (args.length < 2) { + return filterMatchingContains(baseCommands, args[0]); + } + switch (args[0].toLowerCase()) { case "addsong": + return tabCompleteAddSong(args); + case "removesong": + return tabCompleteRemoveSong(minstrelTrait, args); + case "listsongs": + return empty; + case "volume": + case "pitch": if (args.length == 2) { - return soundCategories; + return exampleFloats; } else if (args.length == 3) { - List exampleSongNames = new ArrayList<>(); - exampleSongNames.add("minecraft:records.custom.medieval_3_g_mixolydian"); - exampleSongNames.add("minecraft:block.amethyst_block.step"); - return exampleSongNames; + return booleans; + } else { + return empty; } } return null; } + /** + * Gets the tab-completions for removing a song + * + * @param minstrelTrait

The currently selected minstrel

+ * @param args

THe arguments given by the user

+ * @return

The tab-completion options to display to the user

+ */ + private List tabCompleteRemoveSong(MinstrelTrait minstrelTrait, String[] args) { + if (args.length == 2) { + List indices = new ArrayList<>(); + for (int i = 0; i < minstrelTrait.getPlaylist().getSongs().size(); i++) { + indices.add(String.valueOf(i)); + } + return filterMatchingContains(indices, args[1]); + } else { + return empty; + } + } + + /** + * Gets the tab-completions for adding a song + * + * @param args

The arguments given by the user

+ * @return

The tab-completion options to display to the user

+ */ + private List tabCompleteAddSong(String[] args) { + if (args.length == 2) { + return filterMatchingContains(soundCategories, args[1]); + } else if (args.length == 3) { + List exampleSongNames = new ArrayList<>(); + exampleSongNames.add("minecraft:records.custom.medieval_3_g_mixolydian"); + exampleSongNames.add("minecraft:block.amethyst_block.step"); + return filterMatchingContains(exampleSongNames, args[2]); + } else if (args.length == 4) { + return filterMatchingContains(exampleLengths, args[3]); + } else { + return empty; + } + } + } diff --git a/src/main/java/net/knarcraft/minstrel/util/TabCompletionHelper.java b/src/main/java/net/knarcraft/minstrel/util/TabCompletionHelper.java new file mode 100644 index 0000000..8c1d8a1 --- /dev/null +++ b/src/main/java/net/knarcraft/minstrel/util/TabCompletionHelper.java @@ -0,0 +1,32 @@ +package net.knarcraft.minstrel.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * A helper class for tab-completion + */ +public final class TabCompletionHelper { + + private TabCompletionHelper() { + + } + + /** + * Finds tab complete values that contain the typed text + * + * @param values

The values to filter

+ * @param typedText

The text the player has started typing

+ * @return

The given string values that contain the player's typed text

+ */ + public static List filterMatchingContains(List values, String typedText) { + List configValues = new ArrayList<>(); + for (String value : values) { + if (value.toLowerCase().contains(typedText.toLowerCase())) { + configValues.add(value); + } + } + return configValues; + } + +}