Compare commits

..
Author SHA1 Message Date
benwoo1110 8f03150005 Implement new queue command system. 2021-03-09 10:07:53 +08:00
21 changed files with 820 additions and 477 deletions

No files matched your search

@@ -68,7 +68,6 @@ import com.onarandombox.MultiverseCore.commands.TeleportCommand;
import com.onarandombox.MultiverseCore.commands.UnloadCommand;
import com.onarandombox.MultiverseCore.commands.VersionCommand;
import com.onarandombox.MultiverseCore.commands.WhoCommand;
import com.onarandombox.MultiverseCore.commandtools.queue.CommandQueueManager;
import com.onarandombox.MultiverseCore.destination.AnchorDestination;
import com.onarandombox.MultiverseCore.destination.BedDestination;
import com.onarandombox.MultiverseCore.destination.CannonDestination;
@@ -205,7 +204,6 @@ public class MultiverseCore extends JavaPlugin implements MVPlugin, Core {
// Setup our Map for our Commands using the CommandHandler.
private CommandHandler commandHandler;
private CommandQueueManager commandQueueManager;
private static final String LOG_TAG = "[Multiverse-Core]";
@@ -290,7 +288,6 @@ public class MultiverseCore extends JavaPlugin implements MVPlugin, Core {
// Setup the command manager
this.commandHandler = new CommandHandler(this, this.ph);
this.commandQueueManager = new CommandQueueManager(this);
// Call the Function to assign all the Commands to their Class.
this.registerCommands();
@@ -920,15 +917,6 @@ public class MultiverseCore extends JavaPlugin implements MVPlugin, Core {
return this.commandHandler;
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public CommandQueueManager getCommandQueueManager() {
return commandQueueManager;
}
/**
* Gets the log-tag.
*
@@ -8,7 +8,6 @@
package com.onarandombox.MultiverseCore.api;
import buscript.Buscript;
import com.onarandombox.MultiverseCore.commandtools.queue.CommandQueueManager;
import com.onarandombox.MultiverseCore.destination.DestinationFactory;
import com.onarandombox.MultiverseCore.utils.AnchorManager;
import com.onarandombox.MultiverseCore.utils.MVEconomist;
@@ -87,15 +86,6 @@ public interface Core {
*/
CommandHandler getCommandHandler();
/**
* Manager for command that requires /mv confirm before execution.
*
* @return A non-null {@link CommandQueueManager}.
* @deprecated To be moved to new command manager in 5.0.0
*/
@Deprecated
CommandQueueManager getCommandQueueManager();
/**
* Gets the factory class responsible for loading many different destinations
* on demand.
@@ -33,7 +33,7 @@ public class ConfirmCommand extends MultiverseCommand {
@Override
public void runCommand(CommandSender sender, List<String> args) {
this.plugin.getCommandQueueManager().runQueuedCommand(sender);
this.plugin.getCommandHandler().confirmQueuedCommand(sender);
}
}
@@ -8,7 +8,12 @@
package com.onarandombox.MultiverseCore.commands;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.Core;
import com.onarandombox.MultiverseCore.api.MVWorldManager;
import com.onarandombox.MultiverseCore.commandtools.flag.CoreFlags;
import com.onarandombox.MultiverseCore.commandtools.flag.FlagGroup;
import com.onarandombox.MultiverseCore.commandtools.flag.FlagParseFailedException;
import com.onarandombox.MultiverseCore.commandtools.flag.FlagResult;
import com.pneumaticraft.commandhandler.CommandHandler;
import org.bukkit.ChatColor;
import org.bukkit.World.Environment;
@@ -26,7 +31,16 @@ import java.util.List;
* Creates a new world and loads it.
*/
public class CreateCommand extends MultiverseCommand {
private MVWorldManager worldManager;
private static final FlagGroup FLAG_GROUP = FlagGroup.of(
CoreFlags.SEED,
CoreFlags.WORLD_TYPE,
CoreFlags.GENERATOR,
CoreFlags.GENERATE_STRUCTURES,
CoreFlags.SPAWN_ADJUST
);
private final MVWorldManager worldManager;
public CreateCommand(MultiverseCore plugin) {
super(plugin);
@@ -50,22 +64,7 @@ public class CreateCommand extends MultiverseCommand {
@Override
public void runCommand(CommandSender sender, List<String> args) {
String worldName = args.get(0);
File worldFile = new File(this.plugin.getServer().getWorldContainer(), worldName);
String env = args.get(1);
String seed = CommandHandler.getFlag("-s", args);
String generator = CommandHandler.getFlag("-g", args);
boolean allowStructures = true;
String structureString = CommandHandler.getFlag("-a", args);
if (structureString != null) {
allowStructures = Boolean.parseBoolean(structureString);
}
String typeString = CommandHandler.getFlag("-t", args);
boolean useSpawnAdjust = true;
for (String s : args) {
if (s.equalsIgnoreCase("-n")) {
useSpawnAdjust = false;
}
}
if (this.worldManager.isMVWorld(worldName)) {
sender.sendMessage(ChatColor.RED + "Multiverse cannot create " + ChatColor.GOLD + ChatColor.UNDERLINE
@@ -73,6 +72,7 @@ public class CreateCommand extends MultiverseCommand {
return;
}
File worldFile = new File(this.plugin.getServer().getWorldContainer(), worldName);
if (worldFile.exists()) {
sender.sendMessage(ChatColor.RED + "A Folder/World already exists with this name!");
sender.sendMessage(ChatColor.RED + "If you are confident it is a world you can import with /mvimport");
@@ -86,35 +86,28 @@ public class CreateCommand extends MultiverseCommand {
return;
}
// If they didn't specify a type, default to NORMAL
if (typeString == null) {
typeString = "NORMAL";
}
WorldType type = EnvironmentCommand.getWorldTypeFromString(typeString);
if (type == null) {
sender.sendMessage(ChatColor.RED + "That is not a valid World Type.");
EnvironmentCommand.showWorldTypes(sender);
FlagResult flags;
try {
flags = FLAG_GROUP.calculateResult(args.subList(2, args.size()).toArray(new String[0]));
} catch (FlagParseFailedException e) {
sender.sendMessage(String.format("%sError: %s", ChatColor.RED, e.getMessage()));
return;
}
// Determine if the generator is valid. #918
if (generator != null) {
List<String> genarray = new ArrayList<String>(Arrays.asList(generator.split(":")));
if (genarray.size() < 2) {
// If there was only one arg specified, pad with another empty one.
genarray.add("");
}
if (this.worldManager.getChunkGenerator(genarray.get(0), genarray.get(1), "test") == null) {
// We have an invalid generator.
sender.sendMessage("Invalid generator! '" + generator + "'. " + ChatColor.RED + "Aborting world creation.");
return;
}
}
Command.broadcastCommandMessage(sender, "Starting creation of world '" + worldName + "'...");
if (this.worldManager.addWorld(worldName, environment, seed, type, allowStructures, generator, useSpawnAdjust)) {
if (this.worldManager.addWorld(
worldName,
environment,
flags.getValue(CoreFlags.SEED),
flags.getValue(CoreFlags.WORLD_TYPE),
flags.getValue(CoreFlags.GENERATE_STRUCTURES),
flags.getValue(CoreFlags.GENERATOR),
flags.getValue(CoreFlags.SPAWN_ADJUST))) {
Command.broadcastCommandMessage(sender, "Complete!");
} else {
Command.broadcastCommandMessage(sender, "FAILED.");
return;
}
Command.broadcastCommandMessage(sender, "FAILED.");
}
}
@@ -8,11 +8,9 @@
package com.onarandombox.MultiverseCore.commands;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.commandtools.queue.QueuedCommand;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@@ -37,24 +35,11 @@ public class DeleteCommand extends MultiverseCommand {
public void runCommand(CommandSender sender, List<String> args) {
String worldName = args.get(0);
this.plugin.getCommandQueueManager().addToQueue(new QueuedCommand(
sender,
deleteRunnable(sender, worldName),
String.format("Are you sure you want to delete world '%s'? You cannot undo this action.", worldName)
));
}
private Runnable deleteRunnable(@NotNull CommandSender sender,
@NotNull String worldName) {
return () -> {
sender.sendMessage(String.format("Deleting world '%s'...", worldName));
if (this.plugin.getMVWorldManager().deleteWorld(worldName)) {
sender.sendMessage(String.format("%sWorld %s was deleted!", ChatColor.GREEN, worldName));
return;
}
sender.sendMessage(String.format("%sThere was an issue deleting '%s'! Please check console for errors.",
ChatColor.RED, worldName));
};
Class<?>[] paramTypes = {String.class};
List<Object> objectArgs = new ArrayList<Object>(args);
this.plugin.getCommandHandler()
.queueCommand(sender, "mvdelete", "deleteWorld", objectArgs,
paramTypes, ChatColor.GREEN + "World '" + worldName + "' Deleted!",
ChatColor.RED + "World '" + worldName + "' could NOT be deleted!");
}
}
@@ -8,11 +8,9 @@
package com.onarandombox.MultiverseCore.commands;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.commandtools.queue.QueuedCommand;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@@ -39,30 +37,17 @@ public class RegenCommand extends MultiverseCommand {
@Override
public void runCommand(CommandSender sender, List<String> args) {
String worldName = args.get(0);
boolean useseed = (!(args.size() == 1));
boolean randomseed = (args.size() == 2 && args.get(1).equalsIgnoreCase("-s"));
Boolean useseed = (!(args.size() == 1));
Boolean randomseed = (args.size() == 2 && args.get(1).equalsIgnoreCase("-s"));
String seed = (args.size() == 3) ? args.get(2) : "";
this.plugin.getCommandQueueManager().addToQueue(new QueuedCommand(
sender,
doWorldRegen(sender, worldName, useseed, randomseed, seed),
String.format("Are you sure you want to regen '%s'? You cannot undo this action.", worldName)
));
}
private Runnable doWorldRegen(@NotNull CommandSender sender,
@NotNull String worldName,
boolean useSeed,
boolean randomSeed,
@NotNull String seed) {
return () -> {
if (this.plugin.getMVWorldManager().regenWorld(worldName, useSeed, randomSeed, seed)) {
sender.sendMessage(ChatColor.GREEN + "World Regenerated!");
return;
}
sender.sendMessage(ChatColor.RED + "World could NOT be regenerated!");
};
Class<?>[] paramTypes = {String.class, Boolean.class, Boolean.class, String.class};
List<Object> objectArgs = new ArrayList<Object>();
objectArgs.add(args.get(0));
objectArgs.add(useseed);
objectArgs.add(randomseed);
objectArgs.add(seed);
this.plugin.getCommandHandler().queueCommand(sender, "mvregen", "regenWorld", objectArgs,
paramTypes, ChatColor.GREEN + "World Regenerated!", ChatColor.RED + "World could NOT be regenerated!");
}
}
@@ -11,7 +11,6 @@ import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.Teleporter;
import com.onarandombox.MultiverseCore.api.MVDestination;
import com.onarandombox.MultiverseCore.commandtools.queue.QueuedCommand;
import com.onarandombox.MultiverseCore.destination.CustomTeleporterDestination;
import com.onarandombox.MultiverseCore.destination.DestinationFactory;
import com.onarandombox.MultiverseCore.destination.InvalidDestination;
@@ -166,27 +165,24 @@ public class TeleportCommand extends MultiverseCommand {
if (result == TeleportResult.FAIL_UNSAFE) {
Logging.fine("Could not teleport " + teleportee.getName()
+ " to " + plugin.getLocationManipulation().strCoordsRaw(d.getLocation(teleportee)));
Logging.fine("Queueing Command");
Class<?>[] paramTypes = { CommandSender.class, Player.class, Location.class };
List<Object> items = new ArrayList<Object>();
items.add(teleporter);
items.add(teleportee);
items.add(d.getLocation(teleportee));
String player = "you";
if (!teleportee.equals(teleporter)) {
player = teleportee.getName();
}
this.plugin.getCommandQueueManager().addToQueue(new QueuedCommand(
sender,
doUnsafeTeleport(teleporter, teleportee, d.getLocation(teleportee)),
String.format("%sMultiverse %sdid not teleport %s%s %sto %s%s %sbecause it was unsafe. Would you like to try anyway?",
ChatColor.GREEN, ChatColor.WHITE, ChatColor.AQUA, player, ChatColor.WHITE, ChatColor.DARK_AQUA, d.getName(), ChatColor.WHITE),
UNSAFE_TELEPORT_EXPIRE_DELAY
));
String message = String.format("%sMultiverse %sdid not teleport %s%s %sto %s%s %sbecause it was unsafe.",
ChatColor.GREEN, ChatColor.WHITE, ChatColor.AQUA, player, ChatColor.WHITE, ChatColor.DARK_AQUA, d.getName(), ChatColor.WHITE);
this.plugin.getCommandHandler().queueCommand(sender, "mvteleport", "teleportPlayer", items,
paramTypes, message, "Would you like to try anyway?", "", "", UNSAFE_TELEPORT_EXPIRE_DELAY);
}
// else: Player was teleported successfully (or the tp event was fired I should say)
}
private Runnable doUnsafeTeleport(CommandSender teleporter, Player player, Location location) {
return () -> this.plugin.getSafeTTeleporter().safelyTeleport(teleporter, player, location, false);
}
private boolean checkSendPermissions(CommandSender teleporter, Player teleportee, MVDestination destination) {
if (teleporter.equals(teleportee)) {
if (!this.plugin.getMVPerms().hasPermission(teleporter, "multiverse.teleport.self." + destination.getIdentifier(), true)) {
@@ -0,0 +1,136 @@
package com.onarandombox.MultiverseCore.commandtools.flag;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
/**
* <p>Represents a flag that can be used in commands. This works as a key value pair.</p>
*
* <p>Key is the {@link #identifier} and {@link #aliases} set.</p>
* <p>Value is the {@link T} parsed based on 3 scenarios during command input:</p>
* <ol>
* <li>Flag completely not present. {@link #getDefaultValue()}</li>
* <li>Flag key present but no value. {@link #getValue()}</li>
* <li>Flag key and value present. {@link #getValue(String)}</li>
* </ol>
*
* @param <T> The flag Type.
*/
public abstract class CommandFlag<T> {
protected final String name;
protected final String identifier;
protected final Class<T> type;
protected final Collection<String> aliases;
/**
* @param name Readable name for the flag.
* @param identifier Unique key to identify the flag in command arguments.
* @param type The type of value this flag represents.
*/
protected CommandFlag(@NotNull String name,
@NotNull String identifier,
@NotNull Class<T> type) {
this.name = name;
this.identifier = identifier;
this.type = type;
this.aliases = new ArrayList<>();
}
/**
* Gets name of the Command Flag.
*
* @return The Command Flag name.
*/
@NotNull
public String getName() {
return this.name;
}
/**
* Gets identifier of the Command Flag.
*
* @return The Command Flag identifier.
*/
@NotNull
public String getIdentifier() {
return this.identifier;
}
/**
* Gets {@link T} Type of the Command Flag.
*
* @return The Command Flag type.
*/
@NotNull
public Class<T> getType() {
return this.type;
}
/**
* Gets all the alternative key identifiers set for this Command Flag.
*
* @return Collection of aliases.
*/
@NotNull
public Collection<String> getAliases() {
return this.aliases;
}
/**
* Add alternative key identifiers for this Command Flag.
*
* @param aliases Alias(es) to be added.
* @return A {@link CommandFlag}.
*/
public CommandFlag<T> addAliases(String...aliases) {
Collections.addAll(this.aliases, aliases);
return this;
}
/**
* Tab-complete suggestion for this Command Flag values.
*
* @return Collection of suggested values available.
*/
public abstract Collection<String> suggestValue();
/**
* When this Command Flag can get value by a user input.
*
* @return The {@link T} value.
*/
public abstract T getValue(@NotNull String input) throws FlagParseFailedException;
/**
* When this Command Flag user input value is null/not present.
*
* @return The {@link T} value.
*/
public T getValue() throws FlagParseFailedException {
return null;
}
/**
* When this Command Flag is not present in command input.
*
* @return The {@link T} value.
*/
public T getDefaultValue() {
return null;
}
@Override
public String toString() {
return "CommandFlag{" +
"name='" + name + '\'' +
", identifier='" + identifier + '\'' +
", type=" + type +
", aliases=" + aliases +
'}';
}
}
@@ -0,0 +1,217 @@
package com.onarandombox.MultiverseCore.commandtools.flag;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.utils.webpaste.PasteServiceType;
import org.bukkit.Bukkit;
import org.bukkit.WorldType;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
public class CoreFlags {
private static MultiverseCore multiverse;
public static void setCoreInstance(MultiverseCore plugin) {
multiverse = plugin;
}
/**
* Flag for custom seed.
*/
public static final CommandFlag<String> SEED = new RequiredCommandFlag<String>
("Seed", "--seed", String.class) {
@Override
public Collection<String> suggestValue() {
return Collections.singleton(String.valueOf(new Random().nextLong()));
}
@Override
public String getValue(@NotNull String input) throws FlagParseFailedException {
return input;
}
}.addAliases("-s");
/**
* Flag for custom seed. No value means random seed.
*/
public static final CommandFlag<String> RANDOM_SEED = new OptionalCommandFlag<String>
("Seed", "--seed", String.class) {
@Override
public Collection<String> suggestValue() {
return Collections.singletonList(String.valueOf(new Random().nextLong()));
}
@Override
public String getValue(@NotNull String input) throws FlagParseFailedException {
return input;
}
}.addAliases("-s");
/**
* Flag for world type used.
*/
public static final CommandFlag<WorldType> WORLD_TYPE = new RequiredCommandFlag<WorldType>
("WorldType", "--type", WorldType.class) {
private final Map<String, String> typeAlias = new HashMap<String, String>(4){{
put("normal", "NORMAL");
put("flat", "FLAT");
put("largebiomes", "LARGE_BIOMES");
put("amplified", "AMPLIFIED");
}};
@Override
public Collection<String> suggestValue() {
return typeAlias.keySet();
}
@Override
public WorldType getValue(@NotNull String input) throws FlagParseFailedException {
String typeName = typeAlias.getOrDefault(input, input);
try {
return WorldType.valueOf(input.toUpperCase());
}
catch (IllegalArgumentException e) {
throw new FlagParseFailedException("'%s' is not a valid World Type. See /mv env for available World Types.", input);
}
}
@Override
public WorldType getDefaultValue() {
return WorldType.NORMAL;
}
}.addAliases("-t");
/**
* Flag for world generator.
*/
public static final CommandFlag<String> GENERATOR = new RequiredCommandFlag<String>
("Generator", "--gen", String.class) {
@Override
public Collection<String> suggestValue() {
return Arrays.stream(Bukkit.getServer().getPluginManager().getPlugins())
.filter(Plugin::isEnabled)
.filter(plugin -> multiverse.getUnsafeCallWrapper().wrap(
() -> plugin.getDefaultWorldGenerator("world", ""),
plugin.getName(),
"Get generator"
) != null)
.map(plugin -> plugin.getDescription().getName())
.collect(Collectors.toList());
}
@Override
public String getValue(@NotNull String input) throws FlagParseFailedException {
String[] genArray = input.split(":");
String generator = genArray[0];
String generatorId = (genArray.length > 1) ? genArray[1] : "";
try {
if (multiverse.getMVWorldManager().getChunkGenerator(generator, generatorId, "test") == null) {
throw new Exception();
}
} catch (Exception e) {
throw new FlagParseFailedException("Invalid generator string '%s'. See /mv gens for available generators.", input);
}
return input;
}
}.addAliases("-g");
/**
* Flag to toggle if world should generate structures.
*/
public static final CommandFlag<Boolean> GENERATE_STRUCTURES = new RequiredCommandFlag<Boolean>
("GenerateStructures", "--structures", Boolean.class) {
@Override
public Collection<String> suggestValue() {
return Arrays.asList("true", "false");
}
@Override
public Boolean getValue(@NotNull String input) throws FlagParseFailedException {
return input.equalsIgnoreCase("true");
}
@Override
public Boolean getDefaultValue() {
return true;
}
}.addAliases("--structure", "-a");
/**
* Flag to toggle if world spawn should be adjusted.
*/
public static final CommandFlag<Boolean> SPAWN_ADJUST = new NoValueCommandFlag<Boolean>
("AdjustSpawn", "--dont-adjust-spawn", Boolean.class) {
@Override
public Boolean getValue() throws FlagParseFailedException {
return false;
}
@Override
public Boolean getDefaultValue() {
return true;
}
}.addAliases("-n");
/**
* Flag to specify a paste service.
*/
public static final CommandFlag<PasteServiceType> PASTE_SERVICE_TYPE = new OptionalCommandFlag<PasteServiceType>
("PasteServiceType", "--paste", PasteServiceType.class) {
private final List<String> pasteTypes = Arrays.stream(PasteServiceType.values())
.filter(pt -> pt != PasteServiceType.NONE)
.map(p -> p.toString().toLowerCase())
.collect(Collectors.toList());
@Override
public Collection<String> suggestValue() {
return pasteTypes;
}
@Override
public PasteServiceType getValue(@NotNull String input) throws FlagParseFailedException {
try {
return PasteServiceType.valueOf(input.toUpperCase());
}
catch (IllegalArgumentException e) {
throw new FlagParseFailedException(String.format("Invalid paste service type '%s'.", input));
}
}
@Override
public PasteServiceType getValue() throws FlagParseFailedException {
return PasteServiceType.PASTEGG;
}
@Override
public PasteServiceType getDefaultValue() {
return PasteServiceType.NONE;
}
}.addAliases("-p");
/**
* Flag to toggle if plugin list should be included.
*/
public static final CommandFlag<Boolean> INCLUDE_PLUGIN_LIST = new NoValueCommandFlag<Boolean>
("IncludePlugins", "--include-plugin-list", Boolean.class) {
@Override
public Boolean getValue() throws FlagParseFailedException {
return true;
}
@Override
public Boolean getDefaultValue() {
return true;
}
}.addAliases("-pl");
}
@@ -0,0 +1,96 @@
package com.onarandombox.MultiverseCore.commandtools.flag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A group of {@link CommandFlag}, with indexed keys for efficient lookup.
*/
public class FlagGroup {
/**
* Create a new Flag Group with multiple {@link CommandFlag}.
*
* @param flags Multiple flags.
* @return A new {@link FlagGroup} generated from the flags.
*/
public static FlagGroup of(CommandFlag<?>...flags) {
return new FlagGroup(flags);
}
private final List<String> identifiers;
private final Map<String, CommandFlag<?>> keyMap;
/**
* Create a new Flag Group with multiple {@link CommandFlag}.
*
* @param commandFlags Array of flags
*/
public FlagGroup(CommandFlag<?>[] commandFlags) {
this.identifiers = new ArrayList<>(commandFlags.length);
this.keyMap = new HashMap<>();
for (CommandFlag<?> flag : commandFlags) {
addFlag(flag);
}
}
/**
* Add and indexes a flag.
*
* @param flag The flag to add.
*/
private void addFlag(CommandFlag<?> flag) {
this.identifiers.add(flag.getIdentifier());
this.keyMap.put(flag.getIdentifier(), flag);
for (String flagAlias : flag.getAliases()) {
this.keyMap.put(flagAlias, flag);
}
}
/**
* Parse the arguments to get it's flag values.
*
* @param args The arguments to parse.
* @return A {@link FlagResult} containing value results.
* @throws FlagParseFailedException When there is an error parsing, such as invalid format.
*/
@NotNull
public FlagResult calculateResult(String[] args) throws FlagParseFailedException {
return FlagResult.parse(args,this);
}
/**
* Gets flag from pre-indexed key mapping.
*
* @param key The target key.
* @return A {@link CommandFlag} if found, else null.
*/
@Nullable
public CommandFlag<?> getByKey(String key) {
return this.keyMap.get(key);
}
/**
* Suggest possible identifiers available for this Flag Group.
*
* @return A collection of identifier strings.
*/
@NotNull
public Collection<String> suggestIdentifiers() {
return identifiers;
}
@Override
public String toString() {
return "FlagGroup{" +
"flagIdentifiers=" + identifiers +
", keyFlagMap=" + keyMap +
'}';
}
}
@@ -0,0 +1,27 @@
package com.onarandombox.MultiverseCore.commandtools.flag;
/**
* Thrown when there is an issue with parsing flags from string arguments.
*/
//TODO: extend this from ACF CommandArgumentFailed exception class.
public class FlagParseFailedException extends Exception {
public FlagParseFailedException() {
}
public FlagParseFailedException(String message, Object...replacements) {
super(String.format(message, replacements));
}
public FlagParseFailedException(String message, Throwable cause) {
super(message, cause);
}
public FlagParseFailedException(Throwable cause) {
super(cause);
}
public FlagParseFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
@@ -0,0 +1,198 @@
package com.onarandombox.MultiverseCore.commandtools.flag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* Represents the value result parsed from command arguments.
*/
public class FlagResult {
/**
* Parse arguments into its flag key and values.
*
* @param args The arguments to parse.
* @param flagGroup The flags available to parse into.
* @return The {@link FlagResult} from the parse.
* @throws FlagParseFailedException there is an issue with parsing flags from string arguments.
*/
public static FlagResult parse(@Nullable String[] args,
@NotNull FlagGroup flagGroup) throws FlagParseFailedException {
FlagResult flagResult = new FlagResult();
// No args to parse.
if (args == null || args.length <= 0) {
return flagResult;
}
// First arg must be a flag.
CommandFlag<?> currentFlag = flagGroup.getByKey(args[0]);
boolean completed = false;
// Parse the arguments.
for (int i = 1, argsLength = args.length; i <= argsLength; i++) {
if (currentFlag == null) {
throw new FlagParseFailedException("%s is not a valid flag.", args[i-1]);
}
// This ensures that flag is not null during final parse.
if (i >= argsLength) {
break;
}
String arg = args[i];
if (arg == null) {
throw new FlagParseFailedException("Arguments cannot be null!");
}
CommandFlag<?> nextFlag = flagGroup.getByKey(arg);
// Arg must be a flag key.
if (currentFlag instanceof NoValueCommandFlag) {
flagResult.add(currentFlag, currentFlag.getValue(), false);
currentFlag = nextFlag;
continue;
}
// Arg can be a flag key or value.
if (currentFlag instanceof OptionalCommandFlag) {
if (nextFlag != null) {
// It's a key.
flagResult.add(currentFlag, currentFlag.getValue(), false);
currentFlag = nextFlag;
continue;
}
// It's a value.
flagResult.add(currentFlag, currentFlag.getValue(arg), true);
if (i == argsLength - 1) {
completed = true;
break;
}
currentFlag = flagGroup.getByKey(args[++i]);
continue;
}
// Arg must be a flag value.
if (currentFlag instanceof RequiredCommandFlag) {
if (nextFlag != null) {
// It's a key, error!
throw new FlagParseFailedException("%s flag '%s' requires a value input.",
currentFlag.getName(), currentFlag.getIdentifier());
}
// It's a value.
flagResult.add(currentFlag, currentFlag.getValue(arg), true);
if (i == argsLength - 1) {
completed = true;
break;
}
currentFlag = flagGroup.getByKey(args[++i]);
}
}
// Parse last flag.
if (!completed) {
if (currentFlag instanceof RequiredCommandFlag) {
throw new FlagParseFailedException("%s flag '%s' requires a value input.",
currentFlag.getName(), currentFlag.getIdentifier());
}
flagResult.add(currentFlag, currentFlag.getValue(), false);
}
return flagResult;
}
private final Map<CommandFlag<?>, SingleFlagResult<?>> resultMap;
private FlagResult() {
resultMap = new HashMap<>();
}
/**
* Add a new value result from parsing arguments.
*
* @param flag The flag that the value represents.
* @param value The value of the flag.
* @param fromInput Denotes if flag was parsed by a user input.
*/
private void add(CommandFlag<?> flag, Object value, boolean fromInput) {
resultMap.put(flag, new SingleFlagResult<>(value, fromInput));
}
/**
* Gets value of a flag.
*
* @param flag The flag to get value from.
* @param <T> The type of value.
* @return The value which is associated with the flag.
*/
public <T> T getValue(CommandFlag<T> flag) {
SingleFlagResult<?> result = resultMap.get(flag);
if (result == null) {
return flag.getDefaultValue();
}
return (T) result.value;
}
/**
* Gets if the flag value is by a user input.
*
* @param flag The flag to check against.
* @return True if value is by user input, else false.
*/
public boolean isByUserInput(CommandFlag<?> flag) {
SingleFlagResult<?> result = resultMap.get(flag);
if (result == null) {
return false;
}
return result.isFromUserInput;
}
/**
* Gets if the flag is a default value, and key was not present in user's command arguments.
* i.e. Not in the {@link #resultMap}.
*
* @param flag The flag to check against.
* @return True if flag was not present in user's command arguments, else false.
*/
public boolean isDefaulted(CommandFlag<?> flag) {
return resultMap.get(flag) != null;
}
@Override
public String toString() {
return "FlagResult{" +
"resultMap=" + resultMap +
'}';
}
/**
* Represents a single value result parsed.
* Stores value and addition data such as if value is by user input.
*
* @param <T> The type of value.
*/
private static class SingleFlagResult<T> {
private final T value;
private final boolean isFromUserInput;
/**
* @param value The resultant value from argument parsing.
* @param fromInput Indicates if value is parsed by user input.
*/
private SingleFlagResult(T value, boolean fromInput) {
this.value = value;
this.isFromUserInput = fromInput;
}
@Override
public String toString() {
return "SingleFlagResult{" +
"value=" + value +
", isFromUserInput=" + isFromUserInput +
'}';
}
}
}
@@ -0,0 +1,39 @@
package com.onarandombox.MultiverseCore.commandtools.flag;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
/**
* This flag will always not require a user input to parse value.
*
* @param <T> The flag Type.
*/
public abstract class NoValueCommandFlag<T> extends CommandFlag<T> {
/**
* {@inheritDoc}
*/
public NoValueCommandFlag(String name, String identifier, Class<T> type) {
super(name, identifier, type);
}
/**
* {@link NoValueCommandFlag} will always not require a user input to parse value.
* Thus, no value suggestion needed.
*/
@Override
public final Collection<String> suggestValue() {
return Collections.emptyList();
}
/**
* {@link NoValueCommandFlag} will always not require a user input to parse value.
* Thus, this operation is not allowed.
*/
@Override
public final T getValue(@NotNull String input) throws FlagParseFailedException {
throw new FlagParseFailedException("%s flag '%s' does not require a value.", this.name, this.identifier);
}
}
@@ -0,0 +1,16 @@
package com.onarandombox.MultiverseCore.commandtools.flag;
/**
* Command Flag that optionally allows a user input value.
*
* @param <T> The flag Type.
*/
public abstract class OptionalCommandFlag<T> extends CommandFlag<T> {
/**
* {@inheritDoc}
*/
public OptionalCommandFlag(String name, String identifier, Class<T> type) {
super(name, identifier, type);
}
}
@@ -0,0 +1,25 @@
package com.onarandombox.MultiverseCore.commandtools.flag;
/**
* This flag will always require a user input to parse value.
*
* @param <T> The flag Type.
*/
public abstract class RequiredCommandFlag<T> extends CommandFlag<T> {
/**
* {@inheritDoc}
*/
public RequiredCommandFlag(String name, String identifier, Class<T> type) {
super(name, identifier, type);
}
/**
* {@link RequiredCommandFlag} will always require a user input to parse value.
* Thus, this operation is not allowed.
*/
@Override
public final T getValue() throws FlagParseFailedException {
throw new FlagParseFailedException("%s flag '%s' requires a value input.", this.name, this.identifier);
}
}
@@ -1,158 +0,0 @@
/******************************************************************************
* Multiverse 2 Copyright (c) the Multiverse Team 2020. *
* Multiverse 2 is licensed under the BSD License. *
* For more information please check the README.md file included *
* with this project. *
******************************************************************************/
package com.onarandombox.MultiverseCore.commandtools.queue;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.MultiverseCore;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.block.data.type.CommandBlock;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
import java.util.WeakHashMap;
/**
* Managers the queuing of dangerous commands that needs to use '/mv confirm' before executing.
*/
public class CommandQueueManager {
private static final long TICKS_PER_SECOND = 20;
private static final DummyCommandBlockSender COMMAND_BLOCK = new DummyCommandBlockSender();
private final MultiverseCore plugin;
private final Map<CommandSender, QueuedCommand> queuedCommandMap;
public CommandQueueManager(@NotNull MultiverseCore plugin) {
this.plugin = plugin;
this.queuedCommandMap = new WeakHashMap<>();
}
/**
* Adds a queue command into queue.
*
* @param queuedCommand The queue command to add.
*/
public void addToQueue(QueuedCommand queuedCommand) {
CommandSender targetSender = parseSender(queuedCommand.getSender());
// Since only one command is stored in queue per sender, we remove the old one.
this.removeFromQueue(targetSender);
Logging.finer("Add new command to queue for sender %s.", targetSender);
this.queuedCommandMap.put(targetSender, queuedCommand);
queuedCommand.setExpireTask(runExpireLater(queuedCommand));
queuedCommand.getSender().sendMessage(queuedCommand.getPrompt());
queuedCommand.getSender().sendMessage(String.format("Run %s/mv confirm %sto continue. This will expire in %s seconds.",
ChatColor.GREEN, ChatColor.WHITE, queuedCommand.getValidDuration()));
}
/**
* Expire task that remove {@link QueuedCommand} from queue after valid duration defined.
*
* @param queuedCommand Command to run the expire task on.
* @return The expire {@link BukkitTask}.
*/
@NotNull
private BukkitTask runExpireLater(@NotNull QueuedCommand queuedCommand) {
return Bukkit.getScheduler().runTaskLater(
this.plugin,
expireRunnable(queuedCommand),
queuedCommand.getValidDuration() * TICKS_PER_SECOND
);
}
/**
* Runnable responsible for expiring the queued command.
*
* @param queuedCommand Command to create the expire task on.
* @return The expire runnable.
*/
@NotNull
private Runnable expireRunnable(@NotNull QueuedCommand queuedCommand) {
return () -> {
CommandSender targetSender = parseSender(queuedCommand.getSender());
QueuedCommand matchingQueuedCommand = this.queuedCommandMap.get(targetSender);
if (!queuedCommand.equals(matchingQueuedCommand) || queuedCommand.getExpireTask().isCancelled()) {
// To be safe, but this shouldn't happen since we cancel old commands before add new once.
Logging.finer("This is an old queue command already.");
return;
}
queuedCommand.getSender().sendMessage("Your queued command has expired.");
this.queuedCommandMap.remove(queuedCommand.getSender());
};
}
/**
* Runs the command in queue for the given sender, if any.
*
* @param sender {@link CommandSender} that confirmed the command.
* @return True if queued command ran successfully, else false.
*/
public boolean runQueuedCommand(@NotNull CommandSender sender) {
CommandSender targetSender = parseSender(sender);
QueuedCommand queuedCommand = this.queuedCommandMap.get(targetSender);
if (queuedCommand == null) {
sender.sendMessage(ChatColor.RED + "You do not have any commands in queue.");
return false;
}
Logging.finer("Running queued command...");
queuedCommand.getAction().run();
return removeFromQueue(targetSender);
}
/**
* Since only one command is stored in queue per sender, we remove the old one.
*
* @param sender The {@link CommandSender} that executed the command.
* @return True if queue command is removed from sender successfully, else false.
*/
public boolean removeFromQueue(@NotNull CommandSender sender) {
CommandSender targetSender = parseSender(sender);
QueuedCommand previousCommand = this.queuedCommandMap.remove(targetSender);
if (previousCommand == null) {
Logging.finer("No queue command to remove for sender %s.", targetSender.getName());
return false;
}
previousCommand.getExpireTask().cancel();
Logging.finer("Removed queue command for sender %s.", targetSender.getName());
return true;
}
/**
* To allow all CommandBlocks to be a common sender with use of {@link DummyCommandBlockSender}.
* So confirm command can be used for a queue command on another command block.
*
* @param sender The sender to parse.
* @return The sender, or if its a command block, a {@link DummyCommandBlockSender}.
*/
@NotNull
private CommandSender parseSender(@NotNull CommandSender sender) {
Logging.fine(sender.getClass().getName());
if (isCommandBlock(sender)) {
Logging.finer("Is command block.");
return COMMAND_BLOCK;
}
return sender;
}
/**
* Check if sender is a command block.
*
* @param sender The sender to check on.
* @return True if sender is a command block, else false.
*/
private boolean isCommandBlock(@NotNull CommandSender sender) {
return sender instanceof BlockCommandSender
&& ((BlockCommandSender) sender).getBlock().getBlockData() instanceof CommandBlock;
}
}
@@ -1,104 +0,0 @@
package com.onarandombox.MultiverseCore.commandtools.queue;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
/**
* Used by {@link CommandQueueManager}, so different commands block can be recognised as one.
*/
class DummyCommandBlockSender implements CommandSender {
@Override
public void sendMessage(@NotNull String message) {
throw new UnsupportedOperationException();
}
@Override
public void sendMessage(@NotNull String[] messages) {
throw new UnsupportedOperationException();
}
@Override
public @NotNull Server getServer() {
return Bukkit.getServer();
}
@Override
public @NotNull String getName() {
return "DummyCommandBlockSender";
}
@Override
public boolean isPermissionSet(@NotNull String name) {
throw new UnsupportedOperationException();
}
@Override
public boolean isPermissionSet(@NotNull Permission perm) {
throw new UnsupportedOperationException();
}
@Override
public boolean hasPermission(@NotNull String name) {
throw new UnsupportedOperationException();
}
@Override
public boolean hasPermission(@NotNull Permission perm) {
throw new UnsupportedOperationException();
}
@Override
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) {
throw new UnsupportedOperationException();
}
@Override
public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin) {
throw new UnsupportedOperationException();
}
@Override
public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks) {
throw new UnsupportedOperationException();
}
@Override
public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) {
throw new UnsupportedOperationException();
}
@Override
public void removeAttachment(@NotNull PermissionAttachment attachment) {
throw new UnsupportedOperationException();
}
@Override
public void recalculatePermissions() {
throw new UnsupportedOperationException();
}
@Override
public @NotNull Set<PermissionAttachmentInfo> getEffectivePermissions() {
throw new UnsupportedOperationException();
}
@Override
public boolean isOp() {
return true;
}
@Override
public void setOp(boolean value) {
throw new UnsupportedOperationException();
}
}
@@ -1,86 +0,0 @@
/******************************************************************************
* Multiverse 2 Copyright (c) the Multiverse Team 2020. *
* Multiverse 2 is licensed under the BSD License. *
* For more information please check the README.md file included *
* with this project. *
******************************************************************************/
package com.onarandombox.MultiverseCore.commandtools.queue;
import org.bukkit.command.CommandSender;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull;
/**
* Represents a single command used in {@link CommandQueueManager} for confirming before running potentially
* dangerous action.
*/
public class QueuedCommand {
private static final String DEFAULT_PROMPT_MESSAGE = "The command you are trying to run is deemed dangerous.";
private static final int DEFAULT_VALID_TIME = 10;
private final CommandSender sender;
private final Runnable action;
private final String prompt;
private final int validDuration;
private BukkitTask expireTask;
public QueuedCommand(CommandSender sender, Runnable action) {
this(sender, action, DEFAULT_PROMPT_MESSAGE, DEFAULT_VALID_TIME);
}
public QueuedCommand(CommandSender sender, Runnable action, String prompt) {
this(sender, action, prompt, DEFAULT_VALID_TIME);
}
public QueuedCommand(CommandSender sender, Runnable action, int validDuration) {
this(sender, action, DEFAULT_PROMPT_MESSAGE, validDuration);
}
/**
* Creates a new queue command, to be registered at {@link CommandQueueManager#addToQueue(QueuedCommand)}.
*
* @param sender The sender that ran the command needed for confirmation.
* @param action The logic to execute upon confirming.
* @param prompt Question to ask sender to confirm.
* @param validDuration Duration in which the command is valid for confirm in seconds.
*/
public QueuedCommand(CommandSender sender, Runnable action, String prompt, int validDuration) {
this.sender = sender;
this.action = action;
this.prompt = prompt;
this.validDuration = validDuration;
}
@NotNull
CommandSender getSender() {
return sender;
}
@NotNull
String getPrompt() {
return prompt;
}
int getValidDuration() {
return validDuration;
}
@NotNull
Runnable getAction() {
return action;
}
@NotNull
BukkitTask getExpireTask() {
return expireTask;
}
void setExpireTask(@NotNull BukkitTask expireTask) {
if (this.expireTask != null) {
throw new IllegalStateException("This queue command already has an expire task. You can't register twice!");
}
this.expireTask = expireTask;
}
}
@@ -1,4 +0,0 @@
/**
* Manager queuing of dangerous commands in need of confirmation.
*/
package com.onarandombox.MultiverseCore.commandtools.queue;
@@ -22,5 +22,9 @@ public enum PasteServiceType {
/**
* @see GitHubPasteService
*/
GITHUB
GITHUB,
/**
* No paste service used.
*/
NONE
}
@@ -207,7 +207,7 @@ public class TestWorldStuff {
assertEquals(0, creator.getCore().getMVWorldManager().getMVWorlds().size());
// Verify
verify(mockCommandSender).sendMessage("Invalid generator! 'BogusGen'. " + ChatColor.RED + "Aborting world creation.");
verify(mockCommandSender).sendMessage("§cError: Invalid generator string 'BogusGen'. See /mv gens for available generators.");
}
@Test