Merge pull request 'CommandFramework3' (#94) from CommandFramework3 into master
Reviewed-on: #94 Reviewed-by: Zeanon <thezeanon@gmail.com>
Dieser Commit ist enthalten in:
Commit
3de86209f7
82
SpigotCore_Main/src/de/steamwar/acommand/TestCommand.java
Normale Datei
82
SpigotCore_Main/src/de/steamwar/acommand/TestCommand.java
Normale Datei
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.acommand;
|
||||||
|
|
||||||
|
import de.steamwar.command.SWCommand;
|
||||||
|
import de.steamwar.command.TypeMapper;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class TestCommand extends SWCommand {
|
||||||
|
|
||||||
|
public TestCommand() {
|
||||||
|
// Register this command as 'test'
|
||||||
|
super("test");
|
||||||
|
}
|
||||||
|
|
||||||
|
// One Help Command, the first Parameter should be some kind of CommandSender
|
||||||
|
// The second argument can only be a varArgs string of what arguments were tried to map
|
||||||
|
@Register(help = true)
|
||||||
|
public void testHelp(Player player, String... args) {
|
||||||
|
player.sendMessage("This is your help message");
|
||||||
|
}
|
||||||
|
|
||||||
|
// One Command, the first Parameter should be some kind of CommandSender
|
||||||
|
@Register
|
||||||
|
public void test(Player player) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Another Command, subCommands can be implemented with the Register Annotation,
|
||||||
|
// you can use custom Mappers by putting a Mapper Annotation on a Parameter
|
||||||
|
@Register({"two"})
|
||||||
|
public void testTwo(Player player, int i, @Mapper("solidMaterial") Material material) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Custom Mapper when this command is registered, all Mapper of you class will be
|
||||||
|
// created first and than the Commands. Do not use this mapper outside your class, as
|
||||||
|
// it can only create failures. Use on your own risk. Definition order should be considered.
|
||||||
|
@Mapper("solidMaterial")
|
||||||
|
public TypeMapper<Material> materialTypeMapper() {
|
||||||
|
List<String> tabCompletes = Arrays.stream(Material.values())
|
||||||
|
.filter(Material::isSolid)
|
||||||
|
.map(Material::name)
|
||||||
|
.map(String::toLowerCase)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new TypeMapper<Material>() {
|
||||||
|
@Override
|
||||||
|
public Material map(String[] previous, String s) {
|
||||||
|
return Material.valueOf(s.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> tabCompletes(CommandSender commandSender, String[] previous, String s) {
|
||||||
|
return tabCompletes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
42
SpigotCore_Main/src/de/steamwar/command/CommandParseException.java
Normale Datei
42
SpigotCore_Main/src/de/steamwar/command/CommandParseException.java
Normale Datei
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.command;
|
||||||
|
|
||||||
|
public class CommandParseException extends Exception {
|
||||||
|
|
||||||
|
public CommandParseException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandParseException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandParseException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandParseException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandParseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super(message, cause, enableSuppression, writableStackTrace);
|
||||||
|
}
|
||||||
|
}
|
224
SpigotCore_Main/src/de/steamwar/command/SWCommand.java
Normale Datei
224
SpigotCore_Main/src/de/steamwar/command/SWCommand.java
Normale Datei
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.command;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.IntPredicate;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
public abstract class SWCommand {
|
||||||
|
|
||||||
|
private final Command command;
|
||||||
|
private final List<SubCommand> commandSet = new ArrayList<>();
|
||||||
|
private final List<SubCommand> commandHelpSet = new ArrayList<>();
|
||||||
|
private final Map<String, TypeMapper<?>> localTypeMapper = new HashMap<>();
|
||||||
|
|
||||||
|
protected SWCommand(String command) {
|
||||||
|
this(command, new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SWCommand(String command, String... aliases) {
|
||||||
|
this.command = new Command(command, "", "/" + command, Arrays.asList(aliases)) {
|
||||||
|
@Override
|
||||||
|
public boolean execute(CommandSender sender, String alias, String[] args) {
|
||||||
|
for (SubCommand subCommand : commandSet) {
|
||||||
|
if (subCommand.invoke(sender, args)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (SubCommand subCommand : commandHelpSet) {
|
||||||
|
if (subCommand.invoke(sender, args)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
|
||||||
|
List<String> strings = new ArrayList<>();
|
||||||
|
for (SubCommand subCommand : commandSet) {
|
||||||
|
List<String> tabCompletes = subCommand.tabComplete(sender, args);
|
||||||
|
if (tabCompletes != null) {
|
||||||
|
strings.addAll(tabCompletes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strings = new ArrayList<>(strings);
|
||||||
|
for (int i = strings.size() - 1; i >= 0; i--) {
|
||||||
|
if (!strings.get(i).toLowerCase().startsWith(args[args.length - 1].toLowerCase())) {
|
||||||
|
strings.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
register();
|
||||||
|
|
||||||
|
for (Method method : getClass().getDeclaredMethods()) {
|
||||||
|
addMapper(Mapper.class, method, i -> i == 0, false, TypeMapper.class, (anno, typeMapper) -> {
|
||||||
|
if (anno.local()) {
|
||||||
|
localTypeMapper.put(anno.value(), typeMapper);
|
||||||
|
} else {
|
||||||
|
SWCommandUtils.addMapper(anno.value(), typeMapper);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addMapper(ClassMapper.class, method, i -> i == 0, false, TypeMapper.class, (anno, typeMapper) -> {
|
||||||
|
if (anno.local()) {
|
||||||
|
localTypeMapper.put(anno.value().getTypeName(), typeMapper);
|
||||||
|
} else {
|
||||||
|
SWCommandUtils.addMapper(anno.value().getTypeName(), typeMapper);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
add(Register.class, method, i -> i > 0, true, null, (anno, parameters) -> {
|
||||||
|
if (!anno.help()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (parameters.length != 2) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking parameters or has too many");
|
||||||
|
}
|
||||||
|
if (!parameters[parameters.length - 1].isVarArgs()) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking the varArgs parameters as last Argument");
|
||||||
|
}
|
||||||
|
if (parameters[parameters.length - 1].getType().getComponentType() != String.class) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking the varArgs parameters of type '" + String.class.getTypeName() + "' as last Argument");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
commandHelpSet.add(new SubCommand(this, method, anno.value()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (Method method : getClass().getDeclaredMethods()) {
|
||||||
|
add(Register.class, method, i -> i > 0, true, null, (anno, parameters) -> {
|
||||||
|
if (anno.help()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 1; i < parameters.length; i++) {
|
||||||
|
Parameter parameter = parameters[i];
|
||||||
|
Class<?> clazz = parameter.getType();
|
||||||
|
if (parameter.isVarArgs() && i == parameters.length - 1) {
|
||||||
|
clazz = parameter.getType().getComponentType();
|
||||||
|
}
|
||||||
|
Mapper mapper = parameter.getAnnotation(Mapper.class);
|
||||||
|
if (clazz.isEnum() && mapper == null && !SWCommandUtils.MAPPER_FUNCTIONS.containsKey(clazz.getTypeName())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String name = clazz.getTypeName();
|
||||||
|
if (mapper != null) {
|
||||||
|
name = mapper.value();
|
||||||
|
}
|
||||||
|
if (!SWCommandUtils.MAPPER_FUNCTIONS.containsKey(name)) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, "The parameter '" + parameter.toString() + "' is using an unsupported Mapper of type '" + name + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commandSet.add(new SubCommand(this, method, anno.value(), localTypeMapper));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.commandSet.sort((o1, o2) -> {
|
||||||
|
int compare = Integer.compare(-o1.subCommand.length, -o2.subCommand.length);
|
||||||
|
if (compare != 0) {
|
||||||
|
return compare;
|
||||||
|
} else {
|
||||||
|
int i1 = o1.varArgType != null ? Integer.MAX_VALUE : o1.arguments.length;
|
||||||
|
int i2 = o2.varArgType != null ? Integer.MAX_VALUE : o2.arguments.length;
|
||||||
|
return Integer.compare(i1, i2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
commandHelpSet.sort(Comparator.comparingInt(o -> -o.subCommand.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends Annotation> void add(Class<T> annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class<?> returnType, BiConsumer<T, Parameter[]> consumer) {
|
||||||
|
T anno = SWCommandUtils.getAnnotation(method, annotation);
|
||||||
|
if (anno == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parameter[] parameters = method.getParameters();
|
||||||
|
if (!parameterTester.test(parameters.length)) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking parameters or has too many");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (firstParameter && !CommandSender.class.isAssignableFrom(parameters[0].getType())) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking the first parameter of type '" + CommandSender.class.getTypeName() + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (returnType != null && method.getReturnType() != returnType) {
|
||||||
|
Bukkit.getLogger().log(Level.WARNING, "The method '" + method.toString() + "' is lacking the desired return type '" + returnType.getTypeName() + "'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
consumer.accept(anno, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends Annotation> void addMapper(Class<T> annotation, Method method, IntPredicate parameterTester, boolean firstParameter, Class<?> returnType, BiConsumer<T, TypeMapper<?>> consumer) {
|
||||||
|
add(annotation, method, parameterTester, firstParameter, returnType, (anno, parameters) -> {
|
||||||
|
try {
|
||||||
|
method.setAccessible(true);
|
||||||
|
Object object = method.invoke(this);
|
||||||
|
consumer.accept(anno, (TypeMapper<?>) object);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SecurityException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void unregister() {
|
||||||
|
SWCommandUtils.knownCommandMap.remove(command.getName());
|
||||||
|
for (String alias : command.getAliases()) {
|
||||||
|
SWCommandUtils.knownCommandMap.remove(alias);
|
||||||
|
}
|
||||||
|
command.unregister(SWCommandUtils.commandMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void register() {
|
||||||
|
SWCommandUtils.commandMap.register("steamwar", this.command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.METHOD})
|
||||||
|
protected @interface Register {
|
||||||
|
String[] value() default {};
|
||||||
|
|
||||||
|
boolean help() default false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.PARAMETER, ElementType.METHOD})
|
||||||
|
protected @interface Mapper {
|
||||||
|
String value();
|
||||||
|
|
||||||
|
boolean local() default false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target({ElementType.METHOD})
|
||||||
|
protected @interface ClassMapper {
|
||||||
|
Class<?> value();
|
||||||
|
|
||||||
|
boolean local() default false;
|
||||||
|
}
|
||||||
|
}
|
185
SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java
Normale Datei
185
SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java
Normale Datei
@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.command;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.GameMode;
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.CommandMap;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.command.SimpleCommandMap;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class SWCommandUtils {
|
||||||
|
|
||||||
|
private SWCommandUtils() {
|
||||||
|
throw new IllegalStateException("Utility Class");
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Map<String, TypeMapper<?>> MAPPER_FUNCTIONS = new HashMap<>();
|
||||||
|
|
||||||
|
static final TypeMapper<?> ERROR_FUNCTION = createMapper(s -> {
|
||||||
|
throw new SecurityException();
|
||||||
|
}, s -> Collections.emptyList());
|
||||||
|
|
||||||
|
static final BiFunction<Class<Enum<?>>, String, Enum<?>> ENUM_MAPPER = (enumClass, s) -> {
|
||||||
|
Enum<?>[] enums = enumClass.getEnumConstants();
|
||||||
|
for (Enum<?> e : enums) {
|
||||||
|
if (e.name().equalsIgnoreCase(s)) return e;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
static {
|
||||||
|
addMapper(boolean.class, Boolean.class, createMapper(Boolean::parseBoolean, s -> Arrays.asList("true", "false")));
|
||||||
|
addMapper(float.class, Float.class, createMapper(Float::parseFloat, numberCompleter(Float::parseFloat)));
|
||||||
|
addMapper(double.class, Double.class, createMapper(Double::parseDouble, numberCompleter(Double::parseDouble)));
|
||||||
|
addMapper(int.class, Integer.class, createMapper(Integer::parseInt, numberCompleter(Integer::parseInt)));
|
||||||
|
MAPPER_FUNCTIONS.put(String.class.getTypeName(), createMapper(s -> s, Collections::singletonList));
|
||||||
|
MAPPER_FUNCTIONS.put(Player.class.getTypeName(), createMapper(Bukkit::getPlayer, s -> Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList())));
|
||||||
|
MAPPER_FUNCTIONS.put(GameMode.class.getTypeName(), createMapper(s -> {
|
||||||
|
s = s.toLowerCase();
|
||||||
|
if (s.equals("s") || s.equals("survival") || s.equals("0")) return GameMode.SURVIVAL;
|
||||||
|
if (s.equals("c") || s.equals("creative") || s.equals("1")) return GameMode.CREATIVE;
|
||||||
|
if (s.equals("sp") || s.equals("spectator") || s.equals("3")) return GameMode.SPECTATOR;
|
||||||
|
if (s.equals("a") || s.equals("adventure") || s.equals("2")) return GameMode.ADVENTURE;
|
||||||
|
throw new SecurityException();
|
||||||
|
}, s -> Arrays.asList("s", "survival", "0", "c", "creative", "1", "sp", "specator", "3", "a", "adventure", "2")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addMapper(Class<?> clazz, Class<?> alternativeClazz, TypeMapper<?> mapper) {
|
||||||
|
MAPPER_FUNCTIONS.put(clazz.getTypeName(), mapper);
|
||||||
|
MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final CommandMap commandMap;
|
||||||
|
static final Map<String, Command> knownCommandMap;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
final Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
|
||||||
|
commandMapField.setAccessible(true);
|
||||||
|
commandMap = (CommandMap) commandMapField.get(Bukkit.getServer());
|
||||||
|
} catch (NoSuchFieldException | IllegalAccessException exception) {
|
||||||
|
Bukkit.shutdown();
|
||||||
|
throw new SecurityException("Oh shit. Commands cannot be registered.", exception);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands");
|
||||||
|
knownCommandsField.setAccessible(true);
|
||||||
|
knownCommandMap = (Map<String, Command>) knownCommandsField.get(commandMap);
|
||||||
|
} catch (NoSuchFieldException | IllegalAccessException exception) {
|
||||||
|
Bukkit.shutdown();
|
||||||
|
throw new SecurityException("Oh shit. Commands cannot be registered.", exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Object[] generateArgumentArray(TypeMapper<?>[] parameters, String[] args, Class<?> varArgType, String[] subCommand) throws CommandParseException {
|
||||||
|
Object[] arguments = new Object[parameters.length + 1];
|
||||||
|
int index = 0;
|
||||||
|
while (index < subCommand.length) {
|
||||||
|
if (!args[index].equalsIgnoreCase(subCommand[index])) {
|
||||||
|
throw new CommandParseException();
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varArgType != null && index > args.length - 1) {
|
||||||
|
Object varArgument = Array.newInstance(varArgType, 0);
|
||||||
|
arguments[arguments.length - 1] = varArgument;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < parameters.length - (varArgType != null ? 1 : 0); i++) {
|
||||||
|
arguments[i + 1] = parameters[i].map(Arrays.copyOf(args, index), args[index]);
|
||||||
|
index++;
|
||||||
|
if (arguments[i + 1] == null) {
|
||||||
|
throw new CommandParseException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varArgType != null) {
|
||||||
|
int length = args.length - parameters.length - subCommand.length + 1;
|
||||||
|
Object varArgument = Array.newInstance(varArgType, length);
|
||||||
|
arguments[arguments.length - 1] = varArgument;
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
Object value = parameters[parameters.length - 1].map(Arrays.copyOf(args, index), args[index]);
|
||||||
|
if (value == null) {
|
||||||
|
throw new CommandParseException();
|
||||||
|
}
|
||||||
|
Array.set(varArgument, i, value);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> void addMapper(Class<T> clazz, TypeMapper<T> mapper) {
|
||||||
|
addMapper(clazz.getTypeName(), mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addMapper(String name, TypeMapper<?> mapper) {
|
||||||
|
if (MAPPER_FUNCTIONS.containsKey(name)) return;
|
||||||
|
MAPPER_FUNCTIONS.put(name, mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> TypeMapper<T> createMapper(Function<String, T> mapper, Function<String, List<String>> tabCompleter) {
|
||||||
|
return createMapper(mapper, (commandSender, s) -> tabCompleter.apply(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> TypeMapper<T> createMapper(Function<String, T> mapper, BiFunction<CommandSender, String, List<String>> tabCompleter) {
|
||||||
|
return new TypeMapper<T>() {
|
||||||
|
@Override
|
||||||
|
public T map(String[] previous, String s) {
|
||||||
|
return mapper.apply(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> tabCompletes(CommandSender commandSender, String[] previous, String s) {
|
||||||
|
return tabCompleter.apply(commandSender, s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Function<String, List<String>> numberCompleter(Function<String, ?> mapper) {
|
||||||
|
return s -> {
|
||||||
|
try {
|
||||||
|
mapper.apply(s);
|
||||||
|
return Collections.singletonList(s);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends Annotation> T getAnnotation(Method method, Class<T> annotation) {
|
||||||
|
if (method.getAnnotations().length != 1) return null;
|
||||||
|
return method.getAnnotation(annotation);
|
||||||
|
}
|
||||||
|
}
|
139
SpigotCore_Main/src/de/steamwar/command/SubCommand.java
Normale Datei
139
SpigotCore_Main/src/de/steamwar/command/SubCommand.java
Normale Datei
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.command;
|
||||||
|
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
class SubCommand {
|
||||||
|
|
||||||
|
private SWCommand swCommand;
|
||||||
|
private Method method;
|
||||||
|
String[] subCommand;
|
||||||
|
TypeMapper<?>[] arguments;
|
||||||
|
private Function<CommandSender, ?> commandSenderFunction;
|
||||||
|
Class<?> varArgType = null;
|
||||||
|
|
||||||
|
public SubCommand(SWCommand swCommand, Method method, String[] subCommand) {
|
||||||
|
this(swCommand, method, subCommand, new HashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubCommand(SWCommand swCommand, Method method, String[] subCommand, Map<String, TypeMapper<?>> localTypeMapper) {
|
||||||
|
this.swCommand = swCommand;
|
||||||
|
this.method = method;
|
||||||
|
|
||||||
|
Parameter[] parameters = method.getParameters();
|
||||||
|
commandSenderFunction = sender -> parameters[0].getType().cast(sender);
|
||||||
|
this.subCommand = subCommand;
|
||||||
|
|
||||||
|
arguments = new TypeMapper[parameters.length - 1];
|
||||||
|
for (int i = 1; i < parameters.length; i++) {
|
||||||
|
Parameter parameter = parameters[i];
|
||||||
|
Class<?> clazz = parameter.getType();
|
||||||
|
if (parameter.isVarArgs()) {
|
||||||
|
clazz = clazz.getComponentType();
|
||||||
|
varArgType = clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
SWCommand.Mapper mapper = parameter.getAnnotation(SWCommand.Mapper.class);
|
||||||
|
if (clazz.isEnum() && mapper == null && !SWCommandUtils.MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) {
|
||||||
|
Class<Enum<?>> enumClass = (Class<Enum<?>>) clazz;
|
||||||
|
List<String> tabCompletes = new ArrayList<>();
|
||||||
|
for (Enum<?> enumConstant : enumClass.getEnumConstants()) {
|
||||||
|
tabCompletes.add(enumConstant.name().toLowerCase());
|
||||||
|
}
|
||||||
|
arguments[i - 1] = SWCommandUtils.createMapper(s -> SWCommandUtils.ENUM_MAPPER.apply(enumClass, s), s -> tabCompletes);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = clazz.getTypeName();
|
||||||
|
if (mapper != null) {
|
||||||
|
name = mapper.value();
|
||||||
|
}
|
||||||
|
if (localTypeMapper.containsKey(name)) {
|
||||||
|
arguments[i - 1] = localTypeMapper.getOrDefault(name, SWCommandUtils.ERROR_FUNCTION);
|
||||||
|
} else {
|
||||||
|
arguments[i - 1] = SWCommandUtils.MAPPER_FUNCTIONS.getOrDefault(name, SWCommandUtils.ERROR_FUNCTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean invoke(CommandSender commandSender, String[] args) {
|
||||||
|
if (args.length < arguments.length + subCommand.length - (varArgType != null ? 1 : 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (varArgType == null && args.length > arguments.length + subCommand.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Object[] objects = SWCommandUtils.generateArgumentArray(arguments, args, varArgType, subCommand);
|
||||||
|
objects[0] = commandSenderFunction.apply(commandSender);
|
||||||
|
method.setAccessible(true);
|
||||||
|
method.invoke(swCommand, objects);
|
||||||
|
} catch (IllegalAccessException | RuntimeException | InvocationTargetException e) {
|
||||||
|
throw new SecurityException(e.getMessage(), e);
|
||||||
|
} catch (CommandParseException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> tabComplete(CommandSender commandSender, String[] args) {
|
||||||
|
if (varArgType == null && args.length < arguments.length + subCommand.length - 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<String> argsList = new LinkedList<>(Arrays.asList(args));
|
||||||
|
for (String value : subCommand) {
|
||||||
|
String s = argsList.remove(0);
|
||||||
|
if (argsList.isEmpty()) return Collections.singletonList(value);
|
||||||
|
if (!value.equalsIgnoreCase(s)) return null;
|
||||||
|
}
|
||||||
|
for (TypeMapper<?> argument : arguments) {
|
||||||
|
String s = argsList.remove(0);
|
||||||
|
if (argsList.isEmpty()) return argument.tabCompletes(commandSender, Arrays.copyOf(args, args.length - 1), s);
|
||||||
|
try {
|
||||||
|
if (argument.map(Arrays.copyOf(args, argsList.size()), s) == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (varArgType != null && !argsList.isEmpty()) {
|
||||||
|
while (!argsList.isEmpty()) {
|
||||||
|
String s = argsList.remove(0);
|
||||||
|
if (argsList.isEmpty()) return arguments[arguments.length - 1].tabCompletes(commandSender, Arrays.copyOf(args, args.length - 1), s);
|
||||||
|
try {
|
||||||
|
if (arguments[arguments.length - 1].map(Arrays.copyOf(args, argsList.size()), s) == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
30
SpigotCore_Main/src/de/steamwar/command/TypeMapper.java
Normale Datei
30
SpigotCore_Main/src/de/steamwar/command/TypeMapper.java
Normale Datei
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* This file is a part of the SteamWar software.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020 SteamWar.de-Serverteam
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.steamwar.command;
|
||||||
|
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface TypeMapper<T> {
|
||||||
|
T map(String[] previousArguments, String s);
|
||||||
|
|
||||||
|
List<String> tabCompletes(CommandSender commandSender, String[] previousArguments, String s);
|
||||||
|
}
|
In neuem Issue referenzieren
Einen Benutzer sperren