0.0.2 - Add javadoc and source plugins

This commit is contained in:
Michael Ziluck 2019-05-31 22:46:17 -05:00
parent 9248899c40
commit 18e199bc2a
29 changed files with 12108 additions and 8 deletions

10
.gitignore vendored
View File

@ -1,9 +1,3 @@
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.idea/
*.iml

47
pom.xml Normal file
View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.alttd</groupId>
<artifactId>AltitudeAPI</artifactId>
<version>0.0.2</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.destroystokyo.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.14.1-R0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,50 @@
package com.alttd.altitudeapi;
import java.io.File;
import com.alttd.altitudeapi.utils.items.ItemDb;
import org.bukkit.plugin.java.JavaPlugin;
public class AltitudeAPI extends JavaPlugin
{
private static AltitudeAPI instance;
private static ItemDb itemDb;
public void onEnable()
{
instance = this;
saveDefaultConfig();
if (getConfig().getBoolean("use-items"))
{
saveResource("items.csv", true);
itemDb = new ItemDb(new File(getDataFolder(), "items.csv"));
}
}
/**
* Returns the {@link ItemDb} loaded by this plugin.
*
* @return the {@link ItemDb} loaded by this plugin.
*/
public static ItemDb getItemDb()
{
if (itemDb == null)
{
throw new IllegalStateException("Enable the item api in the config file.");
}
return itemDb;
}
/**
* Returns the singleton instance of this API.
*
* @return the singleton instance of this API.
*/
public static AltitudeAPI getInstance()
{
return instance;
}
}

View File

@ -0,0 +1,352 @@
package com.alttd.altitudeapi.commands;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.bukkit.command.CommandSender;
/**
* @param <T> the type the argument will be after being parsed.
*
* @author Michael Ziluck
*/
public class CommandArgument<T>
{
protected ValidCommand command;
protected T value;
protected String name;
protected boolean optional;
protected boolean variableLength;
protected boolean allowsConsole;
protected int ordinal;
protected List<SenderValidator> senderValidators;
protected List<Validator<T>> validators;
protected Parser<T> parser;
protected String permission;
protected String[] tabOptions;
/**
* Constructs a new argument. This will also initialize the validators list.
*/
protected CommandArgument()
{
this.senderValidators = new LinkedList<>();
this.validators = new LinkedList<>();
}
/**
* Process the given argument. This will parse it into it's appropriate form, as well as validate that it is in the
* proper state. The value returned represents whether or not the process was successful. No error message is
* required beyond what was already sent within the parsers and validators.
*
* @param sender the sender of the command.
* @param label the label of the command.
* @param argument the raw string argument to be processed.
*
* @return {@code true} if the process was successful. Otherwise, returns {@code false}.
*/
protected boolean process(CommandSender sender, String[] label, String argument)
{
if (hasPermission() && !sender.hasPermission(permission))
{
CommandLang.NO_PERMISSION.send(sender);
return false;
}
for (SenderValidator senderValidator : senderValidators)
{
if (!senderValidator.validate(sender))
{
return false;
}
}
value = parser.parseArgument(sender, label, argument);
if (value == null)
{
return false;
}
for (Validator<T> validator : validators)
{
if (!validator.validateArgument(sender, label, value))
{
return false;
}
}
return true;
}
/**
* Retrieves all potential options a player can have when trying to tab-complete this argument. This should always
* be implemented. However, because that most likely will not happen, it defaults to the same behavior that Bukkit
* uses which is to list off the names of all visible connected players.
*
* @param sender the sender who is tab-completing.
* @param lastWord the content of the item so far.
*
* @return all argument options.
*/
protected List<String> getRecommendations(CommandSender sender, String lastWord)
{
List<String> recommendations = parser.getRecommendations(sender, lastWord);
if (recommendations == null)
{
recommendations = CommandHandler.defaultTabComplete(sender, lastWord);
}
return recommendations;
}
/**
* Clears the most recent value processed by this argument.
*/
public void clearValue()
{
value = null;
}
/**
* @return {@code true} if the argument has a required rank. Otherwise returns {@code false}.
*/
public boolean hasPermission()
{
return permission != null;
}
/**
* The ability to use an argument might differ from the ability to use a command in general. This is the rank
* required to use this argument within a command.
*
* @return the rank required to use this argument.
*/
public String getPermission()
{
return permission;
}
/**
* Sets the required permission for this argument. This should only be set once, and should only be changed from the
* CommandArgumentBuilder. Will throw an {@link IllegalStateException} if set a second time.
*
* @param permission the required permission.
*/
public void setPermission(String permission)
{
if (this.permission != null)
{
throw new IllegalArgumentException("Permission can only be set once.");
}
this.permission = permission;
}
/**
* @return the parser for this argument.
*/
public Parser<T> getParser()
{
return parser;
}
/**
* Sets the parser for this argument. This should only be set once, and should only be changed from the
* CommandArgumentBuilder. Will throw an {@link IllegalStateException} if set a second time.
*
* @param parser the argument's parser.
*/
protected void setParser(Parser<T> parser)
{
if (this.parser != null)
{
throw new IllegalArgumentException("Parser can only be set once.");
}
this.parser = parser;
}
/**
* Returns a view of the validators. This view can't be changed, and will throw exceptions if it is attempted.
*
* @return the validators for this argument.
*/
public List<Validator<T>> getValidators()
{
return Collections.unmodifiableList(validators);
}
/**
* Adds a new validator to the argument.
*
* @param validator the new validator.
*/
public void addValidator(Validator<T> validator)
{
this.validators.add(validator);
}
/**
* Returns a view of the sender validators. This view can't be changed, and will throw exception if it is attempted.
*
* @return the sender validators for this argument
*/
public List<SenderValidator> getSenderValidators()
{
return Collections.unmodifiableList(senderValidators);
}
/**
* Adds a new sender validator to the argument.
*
* @param senderValidator the new sender validator.
*/
public void addSenderValidator(SenderValidator senderValidator)
{
this.senderValidators.add(senderValidator);
}
/**
* Gets where in the order of arguments this argument falls for a particular command. The default is -1, so if -1 is
* returned it means that this argument has not yet been assigned to a command.
*
* @return the argument ordinal.
*/
public int getOrdinal()
{
return ordinal;
}
/**
* Sets the order that this argument falls for a particular command. This method is protected to make sure that the
* CommandHandler does not break when managing the argument system.
*
* @param ordinal the new argument ordinal.
*/
protected void setOrdinal(int ordinal)
{
this.ordinal = ordinal;
}
/**
* @return {@code true} if this argument can't be used by console. Otherwise returns {@code false}.
*/
public boolean allowsConsole()
{
return allowsConsole;
}
/**
* @param status {@code true} if this argument can't be used by console.
*/
public void setAllowsConsole(boolean status)
{
this.allowsConsole = status;
}
/**
* @return {@code true} if this argument has variable length. Otherwise returns {@code false}.
*/
public boolean hasVariableLength()
{
return variableLength;
}
/**
* @param status {@code true} if this argument has variable length.
*/
public void setVariableLength(boolean status)
{
this.variableLength = status;
}
/**
* @return whether this argument is optional or not.
*/
public boolean isOptional()
{
return optional;
}
/**
* @param state whether or not the argument is optional.
*/
public void setOptional(boolean state)
{
this.optional = state;
}
/**
* @return the name of the argument
*/
public String getName()
{
return name;
}
/**
* Sets the name for the argument. This should only be set once, and should only be changed from the
* CommandArgumentBuilder. Will throw an {@link IllegalStateException} if set a second time.
*
* @param name the name of the argument.
*/
public void setName(String name)
{
if (this.name != null)
{
throw new IllegalStateException("Name can only be set once.");
}
this.name = name;
}
/**
* This should only be used to check if an optional argument has a value.
*
* @return {@code true} if this argument has a value.
*/
public boolean hasValue()
{
return value != null;
}
/**
* Gets the value that was just parsed and validated.
*
* @return the value.
*/
public T getValue()
{
if (value == null)
{
throw new IllegalStateException("Argument has not been processed.");
}
return value;
}
/**
* @return the command this argument belongs to.
*/
protected ValidCommand getCommand()
{
return command;
}
/**
* @param command the command this argument belongs to.
*/
protected void setCommand(ValidCommand command)
{
this.command = command;
}
}

View File

@ -0,0 +1,139 @@
package com.alttd.altitudeapi.commands;
public class CommandArgumentBuilder<T>
{
private CommandArgument<T> argument;
private CommandArgumentBuilder()
{
argument = new CommandArgument<>();
}
/**
* Sets the name for the argument.
*
* @param name the name for the argument.
*
* @return the same builder.
*/
public CommandArgumentBuilder<T> setName(String name)
{
argument.setName(name);
return this;
}
/**
* Sets the parser for the argument.
*
* @param parser the parser for the argument.
*
* @return the same builder.
*/
public CommandArgumentBuilder<T> setParser(Parser<T> parser)
{
argument.setParser(parser);
return this;
}
/**
* Sets the required rank for the argument.
*
* @param permission the required permission for the argument.
*
* @return the same builder.
*/
public CommandArgumentBuilder<T> setRequiredPermission(String permission)
{
argument.setPermission(permission);
return this;
}
/**
* Adds a validator to be used by the argument.
*
* @param validator the new validator.
*
* @return the same builder.
*/
public CommandArgumentBuilder<T> addValidator(Validator<T> validator)
{
argument.addValidator(validator);
return this;
}
/**
* Adds a sender validator to be used by the argument.
*
* @param senderValidator the new sender validator.
*
* @return the same builder.
*/
public CommandArgumentBuilder<T> addSenderValidator(SenderValidator senderValidator)
{
argument.addSenderValidator(senderValidator);
return this;
}
/**
* Marks this argument as optional. It defaults to false which is why there is no option to disable it as each
* option should only be set once.
*
* @return the same builder.
*/
public CommandArgumentBuilder<T> setOptional()
{
argument.setOptional(true);
return this;
}
/**
* Marks this argument as having variable length. It defaults to false which is why there is no option to disable it
* as each option should only be set to once.
*
* @return the same builder.
*/
public CommandArgumentBuilder<T> setVariableLength()
{
argument.setVariableLength(true);
return this;
}
/**
* Marks this argument as usable by the console. It defaults to false which is why there is no option to disable it
* as each option should only be set once.
*
* @return the same builder
*/
public CommandArgumentBuilder<T> setAllowsConsole()
{
argument.setAllowsConsole(true);
return this;
}
/**
* Returns the {@link CommandArgument} that has been built. Will throw an {@link IllegalStateException} if the
* parser has not been set as it is required.
*
* @return build the completed argument.
*/
public CommandArgument<T> build()
{
if (argument.getName() == null)
{
throw new IllegalArgumentException("Argument name not set");
}
if (argument.getParser() == null)
{
throw new IllegalStateException("Argument parser not set.");
}
return argument;
}
public static <T> CommandArgumentBuilder<T> createBuilder(Class<T> type)
{
return new CommandArgumentBuilder<T>();
}
}

View File

@ -0,0 +1,258 @@
package com.alttd.altitudeapi.commands;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.SimplePluginManager;
import org.bukkit.plugin.java.JavaPlugin;
/**
* The processor that handles all {@link ValidCommand ValidCommands}.
*
* @author Michael Ziluck
*/
public class CommandHandler implements CommandExecutor, TabCompleter
{
private static CommandHandler instance;
private static SimpleCommandMap commandMapInstance = getCommandMap();
private static Map<String, Command> knownCommands = getKnownCommands();
private List<ValidCommand> commands;
/**
* Constructs a new CommandHandler. Will initialize the commands list.
*/
public CommandHandler()
{
this.commands = new LinkedList<>();
}
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args)
{
ValidCommand command = getCommand(label);
if (command != null)
{
if (sender.hasPermission(command.getPermission()))
{
command.process(sender, new String[]{ label }, args);
}
else
{
CommandLang.NO_PERMISSION.send(sender);
}
}
else
{
return false;
}
return true;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command cmd, String alias, String[] args)
{
ValidCommand command = getCommand(alias);
if (command != null)
{
return command.processTabComplete(sender, args);
}
return Collections.emptyList();
}
/**
* Registers the command to be used with this command handler.
*
* @param command the command to register.
* @param plugin the plugin this command is owned by.
*/
public void registerCommand(ValidCommand command, JavaPlugin plugin)
{
// remove any preexisting and conflicting commands.
for (String str : checkString(knownCommands.keySet(), command))
{
knownCommands.remove(str);
}
// create an instance of the bukkit command and set the proper values.
PluginCommand bukkitCommand = createBukkitCommand(command.getName(), plugin);
bukkitCommand.setAliases(Arrays.asList(command.getAliases()));
bukkitCommand.setDescription(command.getDescription());
bukkitCommand.setExecutor(this);
bukkitCommand.setTabCompleter(this);
// register the command with bukkit
commandMapInstance.register(plugin.getDescription().getName(), bukkitCommand);
commands.add(command);
}
/**
* Gets the custom command of the given name. This can be either the command's name or one of it's aliases. Will
* return null if no match was found, which should never happen assuming the command registration went properly.
*
* @param label the label of the command.
*
* @return the command, if one exists.
*/
public ValidCommand getCommand(String label)
{
for (ValidCommand command : commands)
{
if (command.matches(label))
{
return command;
}
}
return null;
}
/**
* Gets all list of all commands that have a name that conflicts with the given valid command. This ensures that the
* commands registered in our system will always supersede any other plugin's commands.
*
* @param strings the names of all existing commands.
* @param command the command to check.
*
* @return all conflicting preexisting commands.
*/
private List<String> checkString(Collection<String> strings, ValidCommand command)
{
List<String> aliases = new ArrayList<>(Arrays.asList(command.getAliases()));
aliases.add(command.getName());
aliases.retainAll(strings);
return aliases;
}
private PluginCommand createBukkitCommand(String name, JavaPlugin plugin)
{
PluginCommand command = null;
try
{
Constructor<PluginCommand> c = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
c.setAccessible(true);
command = c.newInstance(name, plugin);
}
catch (SecurityException | IllegalArgumentException | IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException ex)
{
ex.printStackTrace();
}
return command;
}
/**
* Initializes the singleton instance of this handler.
*/
public static void initialize()
{
instance = new CommandHandler();
}
/**
* @return the singleton instance of this handler.
*/
public static CommandHandler getInstance()
{
return instance;
}
@SuppressWarnings("unchecked")
public static Map<String, Command> getKnownCommands()
{
Map<String, Command> existingCommands = null;
try
{
Field f = SimpleCommandMap.class.getDeclaredField("knownCommands");
f.setAccessible(true);
existingCommands = (Map<String, Command>) f.get(commandMapInstance);
}
catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex)
{
ex.printStackTrace();
}
return existingCommands;
}
private static SimpleCommandMap getCommandMap()
{
SimpleCommandMap commandMap = null;
try
{
if (Bukkit.getPluginManager() instanceof SimplePluginManager)
{
Field f = SimplePluginManager.class.getDeclaredField("commandMap");
f.setAccessible(true);
commandMap = (SimpleCommandMap) f.get(Bukkit.getPluginManager());
}
}
catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex)
{
ex.printStackTrace();
}
return commandMap;
}
/**
* @param sender the sender of the tab complete.
* @param lastWord the beginning of the typed argument.
*
* @return the name of all players visible to the sender.
*/
public static List<String> defaultTabComplete(CommandSender sender, String lastWord)
{
// if the lastWord is null, something went wrong we should exit
if (lastWord == null)
{
return null;
}
// set the argument to lower case for easier comparison
lastWord = lastWord.toLowerCase();
// create the list to return
List<String> values = new LinkedList<>();
// create a player instance of the sender
Player playerSender = sender instanceof Player ? (Player) sender : null;
// go through the players
for (Player player : Bukkit.getOnlinePlayers())
{
// if the sender is not a player or if the player can see the target, add them
if ((playerSender == null || playerSender.canSee(player)) && (lastWord.equals("") || player.getName().toLowerCase().startsWith(lastWord)))
{
values.add(player.getName());
}
}
return values;
}
}

View File

@ -0,0 +1,131 @@
package com.alttd.altitudeapi.commands;
import java.util.Arrays;
import com.alttd.altitudeapi.utils.CollectionUtils;
import com.alttd.altitudeapi.utils.MutableValue;
import com.alttd.altitudeapi.utils.StringUtils;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
public class CommandLang
{
/**
* When a non-player tries to run a player-only command.
*/
public static CommandLang ONLY_PLAYERS = new CommandLang("Only players can run that command.");
/**
* The format for a command usage message.
*/
public static CommandLang USAGE_FORMAT = new CommandLang("&6&lUSAGE &e» &7{message}");
/**
* When a {@link CommandSender} does not have permission to use a command.
*/
public static CommandLang NO_PERMISSION = new CommandLang("&4&lERROR &e» You don't have permission to do that.");
/**
* When a player tries to list the available sub-commands.
*/
public static CommandLang NO_SUBS = new CommandLang("&4&lERROR &e» You don't have access to any sub-commands.");
public static CommandLang HEADER_FOOTER = new CommandLang("&7&m-----------------------------------");
private final MutableValue<String> value;
/**
* Constructs a new CommandLang to be used in this class.
*
* @param value the default message.
*/
private CommandLang(String value)
{
// set the default value
this.value = new MutableValue<>(value);
}
/**
* Returns the value of this lang option.
*
* @return the value of this lang option.
*/
public String getValue()
{
return value.getValue();
}
/**
* Sets the lang message.
*
* @param value the new lang value.
*/
public void setValue(String value)
{
this.value.setValue(value);
}
/**
* Render a string with the proper parameters.
*
* @param args the placeholders and proper content.
*
* @return the rendered string.
*/
private String renderString(Object... args)
{
if (args.length % 2 != 0)
{
throw new IllegalArgumentException("Message rendering requires an even number of arguments. " + Arrays.toString(args) + " given.");
}
String string = getValue();
for (int i = 0; i < args.length; i += 2)
{
string = string.replace(args[i].toString(), CollectionUtils.firstNonNull(args[i + 1], "").toString());
}
return ChatColor.translateAlternateColorCodes('&', string);
}
/**
* Renders this message and returns it.
*
* @param parameters all additional arguments to fill placeholders.
*
* @return the compiled message.
*/
private String getMessage(Object... parameters)
{
return renderString(parameters);
}
/**
* Sends this {@link CommandLang} object to the {@link CommandSender} target. The parameters replace all
* placeholders that exist in the String as well.
*
* @param sender the {@link CommandSender} receiving the message.
* @param parameters all additional arguments to fill placeholders.
*/
public void send(CommandSender sender, Object... parameters)
{
sender.sendMessage(getMessage(parameters));
}
/**
* Sends a usage message to the {@link CommandSender} for a command with the given label and parameters.
*
* @param sender the sender of the message
* @param label the labels the command has.
* @param parameters the parameters a command has.
*/
protected static void sendUsageMessage(CommandSender sender, String[] label, String[] parameters)
{
StringBuilder args = new StringBuilder("/" + StringUtils.compile(label));
for (String str : parameters)
{
args.append(" [").append(str).append("]");
}
sender.sendMessage(USAGE_FORMAT.getMessage("{usage}", args.toString()));
}
}

View File

@ -0,0 +1,50 @@
package com.alttd.altitudeapi.commands;
import java.util.Iterator;
import java.util.List;
import org.bukkit.command.CommandSender;
public interface Parser<T>
{
/**
* Parses the argument from the raw string into the appropriate type. If the argument can't be parsed
*
* @param sender the sender of the command.
* @param label the label of the command
* @param rawArgument the argument to be parsed.
*
* @return the successfully parsed argument.
*/
public T parseArgument(CommandSender sender, String[] label, String rawArgument);
/**
* Get tab complete recommendations for an argument with this given parser. If the default is wanted, it exists in
* {@link CommandHandler#defaultTabComplete(CommandSender, String)}.
*
* @param sender the sender of the tab complete.
* @param lastWord the content of the item so far.
*
* @return the recommendations.
*/
public List<String> getRecommendations(CommandSender sender, String lastWord);
public static void pruneSuggestions(List<String> values, String lastWord)
{
if (values == null || values.size() == 0)
{
return;
}
lastWord = lastWord.toLowerCase();
Iterator<String> it = values.iterator();
while (it.hasNext())
{
if (!it.next().toLowerCase().startsWith(lastWord))
{
it.remove();
}
}
}
}

View File

@ -0,0 +1,16 @@
package com.alttd.altitudeapi.commands;
import org.bukkit.command.CommandSender;
public interface SenderValidator
{
/**
* Validates that the sender is in the proper state.
*
* @param sender the person sending the command.
* @return {@code true} if the sender is valid.
*/
public boolean validate(CommandSender sender);
}

View File

@ -0,0 +1,203 @@
package com.alttd.altitudeapi.commands;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import com.alttd.altitudeapi.utils.StringUtils;
import org.bukkit.command.CommandSender;
public abstract class ValidBaseCommand extends ValidCommand
{
protected List<ValidCommand> subCommands;
/**
* Constructs a new base command.
*
* @param name the name of the command.
* @param description the description of the command.
* @param permission the required rank to use this command.
* @param aliases any aliases for this command.
*/
protected ValidBaseCommand(String name, String description, String permission, String[] aliases)
{
super(name, description, permission, aliases);
subCommands = new LinkedList<>();
}
/**
* Constructs a new base command with no aliases.
*
* @param name the name of the command.
* @param description the description of the command.
* @param permission the required rank to use this command.
*/
protected ValidBaseCommand(String name, String description, String permission)
{
this(name, description, permission, new String[0]);
}
/**
* Constructs a new base command with no required permission.
*
* @param name the name of the command.
* @param description the description of the command.
* @param aliases any aliases for this command.
*/
protected ValidBaseCommand(String name, String description, String[] aliases)
{
this(name, description, null, aliases);
}
/**
* Constructs a new base command with no aliases and no required permission.
*
* @param name the name of the command.
* @param description the description of the command.
*/
protected ValidBaseCommand(String name, String description)
{
this(name, description, null, new String[0]);
}
@Override
protected void process(CommandSender sender, String[] label, String[] rawArguments)
{
ValidCommand sub;
if (rawArguments.length == 0 || (sub = getSubCommand(rawArguments[0])) == null)
{
help(sender, label);
}
else
{
if ((!hasPermission() || sender.hasPermission(getPermission())) && (!sub.hasPermission() || sender.hasPermission(sub.getPermission())))
{
sub.process(sender, StringUtils.add(label, rawArguments[0]), Arrays.copyOfRange(rawArguments, 1, rawArguments.length));
}
else
{
CommandLang.NO_PERMISSION.send(sender);
}
}
}
@Override
public List<String> processTabComplete(CommandSender sender, String[] rawArguments)
{
if (rawArguments.length == 1)
{
return getSubCommandNames(sender, rawArguments[0]);
}
else
{
return getSubCommand(rawArguments[0]).processTabComplete(sender, Arrays.copyOfRange(rawArguments, 1, rawArguments.length));
}
}
/**
* Add a new sub command to this base command.
*
* @param subCommand the sub command to add.
*/
public void addSubCommand(ValidCommand subCommand)
{
subCommands.add(subCommand);
}
/**
* Searches for a sub command by the given name. This can be either the command's name or one of it's aliases.
*
* @param label the label sent by the player.
*
* @return the sub command if one is found.
*/
public ValidCommand getSubCommand(String label)
{
for (ValidCommand command : subCommands)
{
if (command.matches(label))
{
return command;
}
}
return null;
}
/**
* Get the name all sub commands whose name or one if it's aliases starts with the given string. The name for each
* command will be whichever piece was provided, whether that be the alias or the name.
*
* @param sender the sender of the command.
* @param start the beginning of the label.
*
* @return the command labels if any are found.
*/
public List<String> getSubCommandNames(CommandSender sender, String start)
{
List<String> commandNames = new LinkedList<>();
if (!hasPermission() || sender.hasPermission(getPermission()))
{
return commandNames;
}
String match;
for (ValidCommand sub : subCommands)
{
if ((match = sub.getMatchingAlias(start)) != null && (!sub.hasPermission() || sender.hasPermission(sub.getPermission())))
{
commandNames.add(match);
}
}
return commandNames;
}
/**
* Get a view of all the sub commands. This is not able to be modified and doing so will throw an exception.
*
* @return all the sub commands.
*/
public List<ValidCommand> getSubCommands()
{
return Collections.unmodifiableList(subCommands);
}
/**
* Sends the help content to the player.
*
* @param sender the sender of the command.
* @param label the labels that this command has.
*/
public void help(CommandSender sender, String label[])
{
List<ValidCommand> allowedSubs = new LinkedList<>();
for (ValidCommand sub : subCommands)
{
if (!sub.hasPermission() || sender.hasPermission(sub.getPermission()))
{
allowedSubs.add(sub);
}
}
if (allowedSubs.size() == 0)
{
CommandLang.NO_SUBS.send(sender);
return;
}
CommandLang.HEADER_FOOTER.send(sender);
for (ValidCommand sub : allowedSubs)
{
sender.sendMessage(" §b/" + StringUtils.compile(label) + " " + sub.getName() + ": §7" + sub.getDescription());
}
CommandLang.HEADER_FOOTER.send(sender);
}
@Override
public void validRun(CommandSender sender, String[] label, List<CommandArgument<?>> arguments)
{
}
}

View File

@ -0,0 +1,570 @@
package com.alttd.altitudeapi.commands;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.alttd.altitudeapi.utils.CollectionUtils;
import com.alttd.altitudeapi.utils.StringUtils;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
public abstract class ValidCommand
{
protected String name;
protected String description;
protected String permission;
protected boolean blocksConsole;
protected String[] aliases;
protected LinkedList<SenderValidator> senderValidators;
protected ArrayList<CommandArgument<?>> arguments;
protected Table<Integer, Class<?>, Object> values;
/**
* Constructs a new command with the given arguments. This will initialize the arguments list as well as the values
* table. Additionally, it will automatically convert all aliases to lowercase.
*
* @param name the name of the command.
* @param description the description of the command.
* @param permission the required permission for the command.
* @param blocksConsole if this command is unusable by the console.
* @param aliases the aliases of the command.
*/
public ValidCommand(String name, String description, String permission, boolean blocksConsole, String[] aliases)
{
this.name = name;
this.description = description;
this.permission = permission;
this.blocksConsole = blocksConsole;
this.aliases = aliases;
this.senderValidators = new LinkedList<>();
this.arguments = new ArrayList<>();
this.values = HashBasedTable.create();
for (int i = 0; i < aliases.length; i++)
{
aliases[i] = aliases[i] == null ? "" : aliases[i].toLowerCase();
}
}
/**
* Constructs a new command without any aliases.
*
* @param name the name of the command.
* @param description the description of the command.
* @param permission the required permission for the command.
* @param blocksConsole if this command is unusable by the console.
*
* @see #ValidCommand(String, String, String, boolean, String[])
*/
public ValidCommand(String name, String description, String permission, boolean blocksConsole)
{
this(name, description, permission, blocksConsole, new String[0]);
}
/**
* Constructs a new command that is usable by the console.
*
* @param name the name of the command.
* @param description the description of the command.
* @param permission the required permission for the command.
* @param aliases the aliases of the command.
*
* @see #ValidCommand(String, String, String, boolean, String[])
*/
public ValidCommand(String name, String description, String permission, String[] aliases)
{
this(name, description, permission, false, aliases);
}
/**
* Constructs a new command without a required permission.
*
* @param name the name of the command.
* @param description the description of the command.
* @param blocksConsole if this command is unusable by the console.
* @param aliases the aliases of the command.
*
* @see #ValidCommand(String, String, String, boolean, String[])
*/
public ValidCommand(String name, String description, boolean blocksConsole, String[] aliases)
{
this(name, description, null, blocksConsole, aliases);
}
/**
* Constructs a new command without any aliases and is usable by the console.
*
* @param name the name of the command.
* @param description the description of the command.
* @param permission the required permission for the command.
*
* @see #ValidCommand(String, String, String, boolean, String[])
*/
public ValidCommand(String name, String description, String permission)
{
this(name, description, permission, false, new String[0]);
}
/**
* Constructs a new command without any aliases and no required permission.
*
* @param name the name of the command.
* @param description the description of the command.
* @param blocksConsole if this command is unusable by the console.
*
* @see #ValidCommand(String, String, String, boolean, String[])
*/
public ValidCommand(String name, String description, boolean blocksConsole)
{
this(name, description, null, blocksConsole, new String[0]);
}
/**
* Constructs a new command with no required permission and is usable by the console.
*
* @param name the name of the command.
* @param description the description of the command.
* @param aliases the aliases of the command.
*
* @see #ValidCommand(String, String, String, boolean, String[])
*/
public ValidCommand(String name, String description, String[] aliases)
{
this(name, description, null, false, aliases);
}
/**
* Constructs a new command without any aliases, no required permission, and is usable by the console.
*
* @param name the name of the command.
* @param description the description of the command.
*
* @see #ValidCommand(String, String, String, boolean, String[])
*/
public ValidCommand(String name, String description)
{
this(name, description, null, false, new String[0]);
}
/**
* This method runs the command. It goes through each argument and will process and validate it. Then, so long as
* that passes, it will run the command's system. Last it will clear out any saved table data and stored argument
* values.
*
* @param sender the sender of the command.
* @param label the label from which the command was sent
* @param rawArguments the unparsed and non-validated arguments.
*/
protected void process(CommandSender sender, String[] label, String[] rawArguments)
{
if (rawArguments.length < getMinimumLength() || rawArguments.length > getMaximumLength())
{
CommandLang.sendUsageMessage(sender, label, getArgumentNames());
return;
}
for (SenderValidator senderValidator : senderValidators)
{
if (!senderValidator.validate(sender))
{
return;
}
}
if (rawArguments.length == 0 && blocksConsole() && sender instanceof ConsoleCommandSender)
{
CommandLang.ONLY_PLAYERS.send(sender);
return;
}
CommandArgument<?> argument;
for (int i = 0; i < rawArguments.length; i++)
{
argument = getArgument(i);
// this should never happen, it is here exclusively to prevent potential errors that were not caught with exceptions earlier
if (argument == null)
{
CommandLang.sendUsageMessage(sender, label, getArgumentNames());
return;
}
if (blocksConsole() && !argument.allowsConsole() && sender instanceof ConsoleCommandSender)
{
CommandLang.ONLY_PLAYERS.send(sender);
return;
}
if (!argument.process(sender, label, !argument.hasVariableLength() ? rawArguments[i] : StringUtils.compile(Arrays.copyOfRange(rawArguments, i, rawArguments.length))))
{
return;
}
}
try
{
validRun(sender, label, arguments);
}
catch (Exception ex)
{
ex.printStackTrace();
sender.sendMessage("§4An error occurred. Contact a staff member immediately.");
for (Player player : Bukkit.getOnlinePlayers())
{
if (player.hasPermission("altitude.admin"))
{
player.sendMessage("§4§lERROR: §cA player tried to run a FortuneBlocks command and it caused an error. Tell an administrator to check the console.");
}
}
}
arguments.forEach(CommandArgument::clearValue);
clearTable();
}
/**
* Process the tab complete for the given command. This also takes into account the fact the last argument provided
* is what is supposed to be changed. The arguments passed include only the arguments for the command, and not the
* actual label used. The raw arguments passed should always have at least a length of 1.
*
* @param sender the person who sent the tab request.
* @param rawArguments the arguments already typed by the player.
*
* @return the suggestions for tab complete.
*/
public List<String> processTabComplete(CommandSender sender, String[] rawArguments)
{
Iterator<CommandArgument<?>> it = arguments.iterator();
int i = 0;
CommandArgument<?> argument = null;
while (it.hasNext() && i < rawArguments.length)
{
argument = it.next();
i++;
}
if (argument != null)
{
if (!argument.hasPermission() || sender.hasPermission(argument.getPermission()))
{
return argument.getRecommendations(sender, rawArguments[rawArguments.length - 1]);
}
else
{
return Arrays.asList();
}
}
else
{
return CommandHandler.defaultTabComplete(sender, rawArguments[rawArguments.length - 1]);
}
}
/**
* Runs the command after all processing has already been completed. The label is an array of the label used by this
* command as well as any parent command. The arguments will always be there, whether they are used or not. To check
* if optional arguments were used, call the method {@link CommandArgument#hasValue()}.
*
* @param sender the sender of the command.
* @param label the label of the command.
* @param arguments the arguments of the command.
*/
public abstract void validRun(CommandSender sender, String[] label, List<CommandArgument<?>> arguments);
/**
* The table of the already processed values. -1 corresponds to the sender. Every other number corresponds to the
* ordinal of the argument. In the process of running the command, it will always grab the sender's session.
*
* @return the already processed values.
*/
public Table<Integer, Class<?>, Object> getValues()
{
return values;
}
/**
* Clear all the stored values in the table that were added from the command's usage.
*/
private void clearTable()
{
values.clear();
}
/**
* @return an array of all the names of the arguments.
*/
public String[] getArgumentNames()
{
String[] argumentNames = new String[arguments.size()];
int i = 0;
for (CommandArgument<?> argument : arguments)
{
argumentNames[i] = argument.getName();
i++;
}
return argumentNames;
}
/**
* The lowest possible length that the number of raw arguments is capable of being. This is the count of the number
* of arguments that are not optional.
*
* @return the minimum length of the raw command arguments.
*/
protected int getMinimumLength()
{
int minimumLength = 0;
for (CommandArgument<?> argument : arguments)
{
if (!argument.isOptional())
{
minimumLength++;
}
}
return minimumLength;
}
/**
* The highest possible length that the number of raw arguments is capable of being. For non-variable-length
* commands, this is the size of all the arguments. For variable-length commands, it is {@link Integer#MAX_VALUE}.
*
* @return the maximum length of the raw command arguments.
*/
protected int getMaximumLength()
{
if (arguments.size() == 0)
{
return 0;
}
if (CollectionUtils.getLast(arguments).hasVariableLength())
{
return Integer.MAX_VALUE;
}
return arguments.size();
}
/**
* Adds a new argument to be used by the command. Will throw an {@link IllegalArgumentException} in one of two
* cases. First, if the given argument is required and the previous argument is not optional. Second, if the
* argument before it is of variable length. Both of these cases are not supported as there is no perfect way to
* ensure that the arguments will always capture the desired input.
*
* @param argument the new argument
*/
protected void addArgument(CommandArgument<?> argument)
{
if (arguments.size() != 0)
{
if (!argument.isOptional() && CollectionUtils.getLast(arguments).isOptional())
{
throw new IllegalArgumentException("Required arguments can only follow other required arguments.");
}
if (CollectionUtils.getLast(arguments).hasVariableLength())
{
throw new IllegalArgumentException("Arguments of variable length must be the last argument.");
}
}
argument.setOrdinal(arguments.size());
argument.setCommand(this);
arguments.add(argument);
}
/**
* Remove an existing argument from the command.
*
* @param argument the existing argument.
*
* @return whether or not the argument still existed.
*/
protected boolean removeArgument(CommandArgument<?> argument)
{
return arguments.remove(argument);
}
/**
* Remove an existing argument from the command, referenced by name.
*
* @param argumentName the name of the argument.
*
* @return whether or not the argument existed.
*/
protected boolean removeArgument(String argumentName)
{
return removeArgument(getArgument(argumentName));
}
/**
* Return an unmodifiable view of the arguments for this command. To add a new argument use
* {@link #addArgument(CommandArgument)}. To remove an existing argument use
* {@link #removeArgument(CommandArgument)}.
*
* @return the current existing arguments.
*/
public List<CommandArgument<?>> getArguments()
{
return Collections.unmodifiableList(arguments);
}
/**
* @param ordinal the ordinal.
*
* @return the argument at the given ordinal.
*/
protected CommandArgument<?> getArgument(int ordinal)
{
for (CommandArgument<?> argument : arguments)
{
if (argument.getOrdinal() == ordinal)
{
return argument;
}
}
return null;
}
/**
* Get a particular argument that has the given name. This will return null if the argument is not found. Not case
* sensitive.
*
* @param argumentName the name of the argument.
*
* @return the argument with the given name.
*/
protected CommandArgument<?> getArgument(String argumentName)
{
argumentName = argumentName.toLowerCase();
for (CommandArgument<?> arg : arguments)
{
if (arg.getName().equals(argumentName))
{
return arg;
}
}
return null;
}
/**
* Adds a validator to be run on the player before the command itself starts processing the information.
*
* @param senderValidator the new validator
*/
protected void addSenderValidator(SenderValidator senderValidator)
{
senderValidators.add(senderValidator);
}
/**
* Checks whether the passed in command string matches this particular valid command,
*
* @param label the label of the command.
*
* @return {@code true} if the parameter matches the command. Otherwise, returns {@code false}.
*/
protected boolean matches(String label)
{
label = label.toLowerCase();
if (label == null)
{
return false;
}
if (label.equals(getName()))
{
return true;
}
for (String alias : aliases)
{
if (label.equals(alias))
{
return true;
}
}
return false;
}
/**
* @param start the start of the alias to search for.
*
* @return the name or alias that starts with the given string.
*/
protected String getMatchingAlias(String start)
{
start = start.toLowerCase();
if (name.startsWith(start))
{
return name;
}
for (String alias : aliases)
{
if (alias.startsWith(start))
{
return alias;
}
}
return null;
}
/**
* @return all other names this command could be referenced by besides it's name.
*/
public String[] getAliases()
{
return aliases;
}
/**
* @return {@code true} if this command is unusable by the console unless overridden by an argument.
*/
public boolean blocksConsole()
{
return blocksConsole;
}
public boolean hasPermission()
{
return permission != null;
}
/**
* @return the permission required to run this command.
*/
public String getPermission()
{
return permission;
}
/**
* Returns the description of the command. This is given to Bukkit when the command is properly registered within
* their system. There is no method to change this, and if it is changed via reflection, that change will not be
* reflected within Bukkit's command system.
*
* @return the description of the command.
*/
public String getDescription()
{
return description;
}
/**
* Returns the name of the command. This is what is used to register the command within Bukkit, as well as the
* primary way to reference the command elsewhere. There is no method to change this, and if it is changed via
* reflection, that change will not be reflected within Bukkit's command system.
*
* @return the name of the command.
*/
public String getName()
{
return name.toLowerCase();
}
}

View File

@ -0,0 +1,19 @@
package com.alttd.altitudeapi.commands;
import org.bukkit.command.CommandSender;
public interface Validator<T>
{
/**
* Validates that the argument is in the correct state. This should also send any and all error messages associated
* with the problem with the argument.
*
* @param sender the sender of the command.
* @param label the label of the command
* @param arg the argument to be validated.
* @return {@code true} if the argument is valid. {@code false} of the argument is not valid.
*/
public boolean validateArgument(CommandSender sender, String[] label, T arg);
}

View File

@ -0,0 +1,68 @@
package com.alttd.altitudeapi.utils;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
public class ChatUtils
{
private final static int CENTER_PX = 154;
public static void sendCenteredMessage(CommandSender sender, String message)
{
if (message == null || message.equals(""))
{
sender.sendMessage("");
}
message = ChatColor.translateAlternateColorCodes('&', message);
int messagePxSize = 0;
boolean previousCode = false;
boolean isBold = false;
for (char c : message.toCharArray())
{
if (c == ChatColor.COLOR_CHAR)
{
previousCode = true;
}
else if (previousCode)
{
previousCode = false;
isBold = (c == 'l' || c == 'L');
}
else
{
DefaultFontInfo dFI = DefaultFontInfo.getDefaultFontInfo(c);
messagePxSize += isBold ? dFI.getBoldLength() : dFI.getLength();
messagePxSize++;
}
}
int halvedMessageSize = messagePxSize / 2;
int toCompensate = CENTER_PX - halvedMessageSize;
int spaceLength = DefaultFontInfo.SPACE.getLength() + 1;
int compensated = 0;
StringBuilder sb = new StringBuilder();
while (compensated < toCompensate)
{
sb.append(" ");
compensated += spaceLength;
}
sender.sendMessage(sb.toString() + message);
}
public static String renderString(String string, String... arguments)
{
if (arguments.length % 2 != 0)
{
throw new IllegalArgumentException("Must have an even number of arguments.");
}
for (int i = 0; i < arguments.length; i += 2)
{
string = string.replace(arguments[i], arguments[i + 1]);
}
return string;
}
}

View File

@ -0,0 +1,249 @@
package com.alttd.altitudeapi.utils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class CollectionUtils
{
private final static Map<Class<?>, Method> nameMethods = new HashMap<>();
/**
* Retrieves the last value of the given list. If the list is null or it has no elements, this will always return
* null. If the List is also a Deque, this will return the last element using {@link Deque#peekLast()}.
*
* @param list the list to get the last value of.
* @param <T> the type of the list.
*
* @return the last value.
*/
@SuppressWarnings("unchecked")
public static <T> T getLast(List<T> list)
{
if (list == null || list.size() == 0)
{
return null;
}
if (list instanceof Deque)
{
return ((Deque<T>) list).peekLast();
}
return list.get(list.size() - 1);
}
/**
* Safely checks if the given collection is immutable. If the collection is mutable, the data will not be affected
* unless the collection in question keeps track of total number of operations. The test is done by calling
* {@link Collection#removeIf(java.util.function.Predicate)} with the predicate of {@code false}.
*
* @param values the collection to check.
*
* @return {@code true} if the collection is immutable.
*/
public static boolean isImmutable(Collection<?> values)
{
try
{
values.removeIf(x -> false);
return true;
}
catch (UnsupportedOperationException ex)
{
return false;
}
}
/**
* Converts the given values into their string counterpart. This is done by calling {@link Object#toString()} on
* every object. More specific use cases like {@link org.bukkit.entity.Player#getName()} etc are not compatible.
*
* @param values the values to convert.
* @param <T> the type of the collection.
*
* @return the generated list of Strings.
*/
public static <T> List<String> getStringList(Collection<T> values)
{
if (values == null || values.size() == 0)
{
return Collections.emptyList();
}
List<String> list = new LinkedList<>();
for (Object o : values)
{
if (o != null)
{
list.add(o.toString());
}
else
{
list.add(null);
}
}
return list;
}
/**
* Get the names of every single object passed in the values parameter. This method requires the method "getName()"
* to exist within whatever type is passed. If it does not exist, an empty list is returned. However, in the future
* there is a potential that it will be changed to throwing an {@link IllegalArgumentException}.
*
* @param values the values to get the name of.
* @param type the type of the object.
* @param <T> the type of the list.
*
* @return the list of names.
*/
public static <T> List<String> getNames(Collection<T> values, Class<T> type)
{
if (values == null || values.size() == 0)
{
return Collections.emptyList();
}
List<String> list = new LinkedList<>();
Method method = getNameMethod(type);
if (method == null)
{
return Collections.emptyList();
}
for (Object obj : values)
{
try
{
list.add((String) method.invoke(obj));
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
{
// this exception is actually going to be printed as it should never happen.
// the method was set to accessible previously, and it should also never have any arguments.
ex.printStackTrace();
}
}
return list;
}
private static Method getNameMethod(Class<?> clazz)
{
Method method = nameMethods.get(clazz);
if (method == null)
{
try
{
method = clazz.getDeclaredMethod("getName");
method.setAccessible(true);
nameMethods.put(clazz, method);
}
catch (NoSuchMethodException | SecurityException ex)
{
// ignored
}
}
return method;
}
/**
* Searches through the given values for the first non-null value.
*
* @param values the values to find.
* @param <T> the type of the array.
*
* @return the first non-null value.
*/
@SafeVarargs
public static <T> T firstNonNull(T... values)
{
for (T value : values)
{
if (value != null)
{
return value;
}
}
return null;
}
/**
* Converts the given String collection into a String array.
*
* @param collection the collection to convert.
*
* @return the newly created array.
*/
public static String[] toArray(Collection<String> collection)
{
return collection.toArray(new String[0]);
}
/**
* Returns a random value from the collection. If the collection is null or empty, this will return null.
*
* @param collection the collection to poll.
* @param <T> the type of the collection.
*
* @return a random value from the collection.
*/
public static <T> T randomValue(Collection<T> collection)
{
return randomValue(collection, null);
}
/**
* Returns a random value from the collection. If the collection is null or empty, this will return null.
*
* @param collection the collection to poll.
* @param ignored any values not suitable to be included
* @param <T> the type of the collection.
*
* @return a random value from the collection.
*/
public static <T> T randomValue(Collection<T> collection, T... ignored)
{
// if it's null or empty, we don't care
if (collection == null || collection.size() == 0)
{
return null;
}
// if the ignored values aren't null, we need to make them not an option
if (ignored != null)
{
collection = new ArrayList<>(collection);
collection.removeAll(Arrays.asList(ignored));
}
Random random = new Random();
// the index to get a value from
int index = random.nextInt(collection.size());
// if it's a list, we can just get it at that index, no need to iterate
if (collection instanceof List)
{
return ((List<T>) collection).get(index);
}
// it's not a list, time to iterate
Iterator<? extends T> iterator = collection.iterator();
for (int i = 0; iterator.hasNext(); i++)
{
if (i == index)
{
return iterator.next();
}
iterator.next();
}
return null;
}
}

View File

@ -0,0 +1,139 @@
package com.alttd.altitudeapi.utils;
public enum DefaultFontInfo
{
A('A', 5),
a('a', 5),
B('B', 5),
b('b', 5),
C('C', 5),
c('c', 5),
D('D', 5),
d('d', 5),
E('E', 5),
e('e', 5),
F('F', 5),
f('f', 4),
G('G', 5),
g('g', 5),
H('H', 5),
h('h', 5),
I('I', 3),
i('i', 1),
J('J', 5),
j('j', 5),
K('K', 5),
k('k', 4),
L('L', 5),
l('l', 1),
M('M', 5),
m('m', 5),
N('N', 5),
n('n', 5),
O('O', 5),
o('o', 5),
P('P', 5),
p('p', 5),
Q('Q', 5),
q('q', 5),
R('R', 5),
r('r', 5),
S('S', 5),
s('s', 5),
T('T', 5),
t('t', 4),
U('U', 5),
u('u', 5),
V('V', 5),
v('v', 5),
W('W', 5),
w('w', 5),
X('X', 5),
x('x', 5),
Y('Y', 5),
y('y', 5),
Z('Z', 5),
z('z', 5),
NUM_1('1', 5),
NUM_2('2', 5),
NUM_3('3', 5),
NUM_4('4', 5),
NUM_5('5', 5),
NUM_6('6', 5),
NUM_7('7', 5),
NUM_8('8', 5),
NUM_9('9', 5),
NUM_0('0', 5),
EXCLAMATION_POINT('!', 1),
AT_SYMBOL('@', 6),
NUM_SIGN('#', 5),
DOLLAR_SIGN('$', 5),
PERCENT('%', 5),
UP_ARROW('^', 5),
AMPERSAND('&', 5),
ASTERISK('*', 5),
LEFT_PARENTHESIS('(', 4),
RIGHT_PERENTHESIS(')', 4),
MINUS('-', 5),
UNDERSCORE('_', 5),
PLUS_SIGN('+', 5),
EQUALS_SIGN('=', 5),
LEFT_CURL_BRACE('{', 4),
RIGHT_CURL_BRACE('}', 4),
LEFT_BRACKET('[', 3),
RIGHT_BRACKET(']', 3),
COLON(':', 1),
SEMI_COLON(';', 1),
DOUBLE_QUOTE('"', 3),
SINGLE_QUOTE('\'', 1),
LEFT_ARROW('<', 4),
RIGHT_ARROW('>', 4),
QUESTION_MARK('?', 5),
SLASH('/', 5),
BACK_SLASH('\\', 5),
LINE('|', 1),
TILDE('~', 5),
TICK('`', 2),
PERIOD('.', 1),
COMMA(',', 1),
SPACE(' ', 3),
DEFAULT('a', 4);
private char character;
private int length;
DefaultFontInfo(char character, int length)
{
this.character = character;
this.length = length;
}
public char getCharacter()
{
return this.character;
}
public int getLength()
{
return this.length;
}
public int getBoldLength()
{
if (this == DefaultFontInfo.SPACE)
return this.getLength();
return this.length + 1;
}
public static DefaultFontInfo getDefaultFontInfo(char c)
{
for (DefaultFontInfo dFI : DefaultFontInfo.values())
{
if (dFI.getCharacter() == c)
return dFI;
}
return DefaultFontInfo.DEFAULT;
}
}

View File

@ -0,0 +1,46 @@
package com.alttd.altitudeapi.utils;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
/**
* Utils for working with enums. Most of the code was taken EssentialsX.
*
* @author Michael Ziluck
*/
public class EnumUtil
{
/**
* Return a set containing <b>all</b> fields of the given enum that maths one of the provided
* names.
*
* @param enumClass The class to search through
* @param names The names of the fields to search for
* @param <T> The enum to search through
*
* @return All matching enum fields
*/
public static <T extends Enum> Set<T> getAllMatching(Class<T> enumClass, String... names)
{
Set<T> set = new HashSet<>();
for (String name : names)
{
try
{
Field enumField = enumClass.getDeclaredField(name);
if (enumField.isEnumConstant())
{
set.add((T) enumField.get(null));
}
}
catch (NoSuchFieldException | IllegalAccessException ignored)
{
}
}
return set;
}
}

View File

@ -0,0 +1,27 @@
package com.alttd.altitudeapi.utils;
import java.util.Set;
import org.bukkit.Material;
public class ItemUtils
{
private static final Set<Material> POTIONS;
static
{
POTIONS = EnumUtil.getAllMatching(Material.class, "POTION", "SPLASH_POTION", "LINGERING_POTION", "TIPPED_ARROW");
}
/**
* Checks if the given material is a potion.
*
* @param material the material to check.
*
* @return {@code true} if the material is a potion. Otherwise returns {@code false}.
*/
public static boolean isPotion(Material material)
{
return POTIONS.contains(material);
}
}

View File

@ -0,0 +1,55 @@
package com.alttd.altitudeapi.utils;
/**
* Designed to wrap enums to force them to behave mutably.
*
* @param <T> the type this MutableEnum wraps.
*
* @author Michael Ziluck
*/
public class MutableEnum<T extends Enum<T>>
{
private Enum<T> value;
/**
* Creates a new MutableEnum wrapper for the given value.
*
* @param value the value to wrap.
*/
public MutableEnum(T value)
{
this.value = value;
}
/**
* @return the currently wrapped value.
*/
public Enum<T> getValue()
{
return value;
}
/**
* The value can't be null.
*
* @param value the new wrapped value.
*/
public void setValue(T value)
{
if (value == null)
{
throw new NullPointerException("Value can't be null.");
}
this.value = value;
}
/**
* @return the class type of the contained Enum.
*/
public Class<T> getType()
{
return value.getDeclaringClass();
}
}

View File

@ -0,0 +1,45 @@
package com.alttd.altitudeapi.utils;
/**
* Designed to wrap Strings to force them to behave mutably.
*
* @author Michael Ziluck
*/
public class MutableString
{
private String value;
/**
* Creates a new MutableString wrapper for the given value.
*
* @param value the value to wrap.
*/
public MutableString(String value)
{
this.value = value;
}
/**
* @return the currently wrapped value.
*/
public String getValue()
{
return value;
}
/**
* The value can't be null.
*
* @param value the new wrapped value.
*/
public void setValue(String value)
{
if (value == null)
{
throw new NullPointerException("Value can't be null.");
}
this.value = value;
}
}

View File

@ -0,0 +1,60 @@
package com.alttd.altitudeapi.utils;
/**
* Represents a mutable data type for a type that may not normally be mutable, either because it is final, primitive, or sealed.
*
* @param <T> the type of this mutable value.
*/
public class MutableValue<T>
{
private T value;
/**
* Constructs a new MutableValue with the given object.
*
* @param t the value to be stored.
*/
public MutableValue(T t)
{
if (t == null)
{
throw new IllegalArgumentException("Value can't be null.");
}
this.value = t;
}
/**
* Returns the value that is currently stored. If there is no value, returns null.
*
* @return the value that is currently stored.
*/
public T getValue()
{
return value;
}
/**
* Sets the value that is currently stored.
*
* @param t the new value to be stored.
*/
public void setValue(T t)
{
if (t == null)
{
throw new IllegalArgumentException("Value can't be null.");
}
this.value = t;
}
public Class<T> getType()
{
if (value == null)
{
throw new IllegalStateException("Value can't be null.");
}
return (Class<T>) value.getClass();
}
}

View File

@ -0,0 +1,157 @@
package com.alttd.altitudeapi.utils;
import java.text.DecimalFormat;
public class StringUtils
{
public static String implode(String[] strings, int start, int end)
{
StringBuilder sb = new StringBuilder();
for (int i = start; i < end; i++)
{
sb.append(strings[i] + " ");
}
return sb.toString().trim();
}
public static String[] add(String[] array, String add)
{
String[] values = new String[array.length + 1];
for (int i = 0; i < array.length; i++)
{
values[i] = array[i];
}
values[array.length] = add;
return values;
}
public static String compile(String[] strings)
{
return implode(strings, 0, strings.length);
}
public static String capitalize(final String str)
{
int strLen;
if (str == null || (strLen = str.length()) == 0)
{
return str;
}
final int firstCodepoint = str.codePointAt(0);
final int newCodePoint = Character.toTitleCase(firstCodepoint);
if (firstCodepoint == newCodePoint)
{
// already capitalized
return str;
}
final int newCodePoints[] = new int[strLen]; // cannot be longer than
// the char array
int outOffset = 0;
newCodePoints[outOffset++] = newCodePoint; // copy the first codepoint
for (int inOffset = Character.charCount(firstCodepoint); inOffset < strLen;)
{
final int codepoint = str.codePointAt(inOffset);
newCodePoints[outOffset++] = codepoint; // copy the remaining ones
inOffset += Character.charCount(codepoint);
}
return new String(newCodePoints, 0, outOffset);
}
public static boolean contains(String[] values, String search)
{
for (String val : values)
{
if (val.equalsIgnoreCase(search))
{
return true;
}
}
return false;
}
public static boolean isNullOrEmpty(String str)
{
return str == null || str.length() == 0;
}
public static boolean isWhitespace(String str)
{
if (str == null)
{
return false;
}
final int sz = str.length();
for (int i = 0; i < sz; i++)
{
if (!Character.isWhitespace(str.charAt(i)))
{
return false;
}
}
return true;
}
public static boolean containsAny(String search, String... strings)
{
if (isNullOrEmpty(search))
{
return false;
}
for (String searchCharSequence : strings)
{
if (indexOf(search, searchCharSequence, 0) >= 0)
{
return true;
}
}
return false;
}
private static int indexOf(CharSequence cs, CharSequence searchChar, int start)
{
return cs.toString().indexOf(searchChar.toString(), start);
}
public static String formatNumber(Number number, int decimalPlaces, boolean useCommas)
{
StringBuilder sb = new StringBuilder();
if (useCommas)
{
sb.append("#,##0");
}
else
{
sb.append("0");
}
if (decimalPlaces > 0)
{
sb.append('.');
for (int i = 0; i < decimalPlaces; i++)
{
sb.append('0');
}
}
return new DecimalFormat(sb.toString()).format(number);
}
public static String doubleFormat(double number)
{
String formatted;
if (number % 1 == 0)
{
formatted = Integer.toString((int) number);
}
else
{
formatted = Double.toString(number);
}
return formatted;
}
}

View File

@ -0,0 +1,22 @@
package com.alttd.altitudeapi.utils;
import org.bukkit.Material;
import org.bukkit.event.block.BlockBreakEvent;
public class VersionUtils
{
public static void stopDrops(BlockBreakEvent event)
{
try
{
event.setDropItems(false);
}
catch (NoSuchMethodError ex)
{
event.setCancelled(true);
event.getBlock().setType(Material.AIR);
}
}
}

View File

@ -0,0 +1,99 @@
package com.alttd.altitudeapi.utils.items;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.potion.PotionData;
public class ItemData
{
private final Material material;
private PotionData potionData = null;
private EntityType entity = null;
public ItemData(Material material)
{
this.material = material;
}
public ItemData(Material material, PotionData potionData)
{
this.material = material;
this.potionData = potionData;
}
public ItemData(Material material, EntityType entity)
{
this.material = material;
this.entity = entity;
}
public Material getMaterial()
{
return material;
}
public PotionData getPotionData()
{
return this.potionData;
}
public EntityType getEntity()
{
return this.entity;
}
@Override
public int hashCode()
{
return (31 * material.hashCode()) ^ potionData.hashCode();
}
@Override
public boolean equals(Object o)
{
if (o == null)
{
return false;
}
if (!(o instanceof ItemData))
{
return false;
}
ItemData that = (ItemData) o;
return this.material == that.getMaterial() && potionDataEquals(that) && entityEquals(that);
}
private boolean potionDataEquals(ItemData o)
{
if (this.potionData == null && o.getPotionData() == null)
{
return true;
}
else if (this.potionData != null && o.getPotionData() != null)
{
return this.potionData.equals(o.getPotionData());
}
else
{
return false;
}
}
private boolean entityEquals(ItemData o)
{
if (this.entity == null && o.getEntity() == null)
{ // neither have an entity
return true;
}
else if (this.entity != null && o.getEntity() != null)
{ // both have an entity; check if it's the same one
return this.entity.equals(o.getEntity());
}
else
{ // one has an entity but the other doesn't, so they can't be equal
return false;
}
}
}

View File

@ -0,0 +1,279 @@
package com.alttd.altitudeapi.utils.items;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.alttd.altitudeapi.AltitudeAPI;
import com.alttd.altitudeapi.utils.ItemUtils;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BlockStateMeta;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.PotionMeta;
import org.bukkit.potion.PotionData;
public class ItemDb
{
private static Gson gson = new Gson();
// Maps primary name to ItemData
private final transient Map<String, ItemData> items = new HashMap<>();
// Maps alias to primary name
private final transient Map<String, String> itemAliases = new HashMap<>();
// Every known alias
private final transient Set<String> allAliases = new HashSet<>();
private transient File file;
private boolean ready = false;
public ItemDb(File file)
{
this.file = file;
reloadConfig();
}
public void reloadConfig()
{
if (file == null)
{
file = new File(AltitudeAPI.getInstance().getDataFolder(), "items.json");
}
this.rebuild();
AltitudeAPI.getInstance().getLogger().info(String.format("Loaded %s items from items.json.", listNames().size()));
}
private void rebuild()
{
this.reset();
String json = getLines(file).stream()
.filter(line -> !line.startsWith("#"))
.collect(Collectors.joining());
this.loadJSON(String.join("\n", json));
ready = true;
}
private void reset()
{
ready = false;
items.clear();
itemAliases.clear();
allAliases.clear();
}
public void loadJSON(String source)
{
JsonObject map = (new JsonParser()).parse(source).getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : map.entrySet())
{
String key = entry.getKey();
JsonElement element = entry.getValue();
boolean valid = false;
if (element.isJsonObject())
{
ItemData data = gson.fromJson(element, ItemData.class);
items.put(key, data);
valid = true;
}
else
{
try
{
String target = element.getAsString();
itemAliases.put(key, target);
valid = true;
}
catch (Exception ignored)
{
}
}
if (valid)
{
allAliases.add(key);
}
else
{
AltitudeAPI.getInstance().getLogger().warning(String.format("Failed to add item: \"%s\": %s", key, element.toString()));
}
}
}
public ItemStack get(String id) throws Exception
{
id = id.toLowerCase();
final String[] split = id.split(":");
ItemData data = getByName(split[0]);
if (data == null)
{
throw new Exception("Unknown item name: " + id);
}
Material material = data.getMaterial();
if (!material.isItem())
{
throw new Exception("Cannot spawn " + id + "; this is not a spawnable item.");
}
ItemStack stack = new ItemStack(material);
stack.setAmount(material.getMaxStackSize());
PotionData potionData = data.getPotionData();
ItemMeta meta = stack.getItemMeta();
if (potionData != null && meta instanceof PotionMeta)
{
PotionMeta potionMeta = (PotionMeta) meta;
potionMeta.setBasePotionData(potionData);
}
// For some reason, Damageable doesn't extend ItemMeta but CB implements them in the same
// class. As to why, your guess is as good as mine.
if (split.length > 1 && meta instanceof Damageable)
{
Damageable damageMeta = (Damageable) meta;
damageMeta.setDamage(Integer.parseInt(split[1]));
}
EntityType entity = data.getEntity();
if (entity != null && material.toString().contains("SPAWNER"))
{
BlockStateMeta bsm = (BlockStateMeta) meta;
BlockState bs = bsm.getBlockState();
((CreatureSpawner) bs).setSpawnedType(entity);
bsm.setBlockState(bs);
}
stack.setItemMeta(meta);
return stack;
}
private ItemData getByName(String name)
{
name = name.toLowerCase();
if (items.containsKey(name))
{
return items.get(name);
}
else if (itemAliases.containsKey(name))
{
return items.get(itemAliases.get(name));
}
return null;
}
public List<String> nameList(ItemStack item)
{
List<String> names = new ArrayList<>();
String primaryName = name(item);
names.add(primaryName);
for (Map.Entry<String, String> entry : itemAliases.entrySet())
{
if (entry.getValue().equalsIgnoreCase(primaryName))
{
names.add(entry.getKey());
}
}
return names;
}
public String name(ItemStack item)
{
ItemData data = lookup(item);
for (Map.Entry<String, ItemData> entry : items.entrySet())
{
if (entry.getValue().equals(data))
{
return entry.getKey();
}
}
return null;
}
public ItemData lookup(ItemStack item)
{
Material type = item.getType();
if (ItemUtils.isPotion(type) && item.getItemMeta() instanceof PotionMeta)
{
PotionData potion = ((PotionMeta) item.getItemMeta()).getBasePotionData();
return new ItemData(type, potion);
}
else if (type.toString().contains("SPAWNER"))
{
EntityType entity = ((CreatureSpawner) ((BlockStateMeta) item.getItemMeta()).getBlockState()).getSpawnedType();
return new ItemData(type, entity);
}
else
{
return new ItemData(type);
}
}
public Collection<String> listNames()
{
return Collections.unmodifiableSet(allAliases);
}
private List<String> getLines(File file)
{
try (final BufferedReader reader = new BufferedReader(new FileReader(file)))
{
final List<String> lines = new ArrayList<>(9000);
String line = null;
do
{
if ((line = reader.readLine()) == null)
{
break;
}
lines.add(line);
} while (true);
return lines;
}
catch (IOException ex)
{
return Collections.emptyList();
}
}
}

View File

@ -0,0 +1 @@
use-items: false

8976
src/main/resources/items.csv Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
only-players: "Only players can run that command."
usage-format: "&6&lUSAGE &e» &7{message}"
no-permission: "&4&lERROR &e» You don't have permission to do that."
no-subs: "&4&lERROR &e» You don't have access to any sub-commands."
header-footer: "&7&m-----------------------------------"

View File

@ -0,0 +1,4 @@
main: com.alttd.altitudeapi.AltitudeAPI
name: AltitudeAPI
version: ${project.version}
author: Michael Ziluck

View File

@ -0,0 +1,39 @@
package com.alttd.altitudeapi.commands;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class CommandLangTest
{
private FileConfiguration config;
@Before
public void setup()
{
File file = new File("src/main/resources/lang.yml");
config = YamlConfiguration.loadConfiguration(file);
}
@Test
public void test_file_contains_options()
{
Field[] fields = CommandLang.class.getDeclaredFields();
for (Field field : fields)
{
if (Modifier.isStatic(field.getModifiers()))
{
assertTrue("Missing value in file: " + field.getName(), config.contains(field.getName().toLowerCase().replace("_", "-")));
}
}
}
}