Adds command documentation and improves tab-completion

Adds documentation for all commands to the README
Adds option to force an update if changing the pitch or volume of a minstrel
Adds tab-completions for removeSong, listSongs, volume and pitch commands
Adds filtering for tab-completions based on current input
This commit is contained in:
Kristian Knarvik 2022-10-30 22:38:40 +01:00
parent 9db2f871c6
commit 9fd25b90f7
4 changed files with 169 additions and 13 deletions

View File

@ -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.
there may be silent minstrels until the next song plays.
## Commands
| Command | Arguments | Description |
| --- | --- | --- |
| /minstrel addsong | <sound category> <sound identifier> <duration> | Adds the specified song to the selected minstrel's playlist |
| /minstrel removesong | <index (0-10000)> | 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 | <new volume (0.01 to 10000)> \[force update (true/false)] | Displays or sets the volume of the selected minstrel |
| /minstrel pitch | <new pitch (0.01 to 10000)> \[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.

View File

@ -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 <p>The sender to send error/success messages to</p>
* @return <p>True if the pitch was successfully updated</p>
*/
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 <p>The sender to send error/success messages to</p>
* @return <p>True if the volume was successfully updated</p>
*/
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!");

View File

@ -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<String> baseCommands;
private final List<String> soundCategories;
private final List<String> exampleLengths;
private final List<String> empty = new ArrayList<>();
private final List<String> exampleFloats;
private final List<String> 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<String> 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<String> 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 <p>The currently selected minstrel</p>
* @param args <p>THe arguments given by the user</p>
* @return <p>The tab-completion options to display to the user</p>
*/
private List<String> tabCompleteRemoveSong(MinstrelTrait minstrelTrait, String[] args) {
if (args.length == 2) {
List<String> 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 <p>The arguments given by the user</p>
* @return <p>The tab-completion options to display to the user</p>
*/
private List<String> tabCompleteAddSong(String[] args) {
if (args.length == 2) {
return filterMatchingContains(soundCategories, args[1]);
} else if (args.length == 3) {
List<String> 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;
}
}
}

View File

@ -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 <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values that contain the player's typed text</p>
*/
public static List<String> filterMatchingContains(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().contains(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
}