CMDoS #110
31
SpigotCore_14/src/de/steamwar/command/Dispatcher_14.java
Normale Datei
31
SpigotCore_14/src/de/steamwar/command/Dispatcher_14.java
Normale Datei
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 com.mojang.brigadier.CommandDispatcher;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_14_R1.CraftServer;
|
||||
|
||||
public class Dispatcher_14 {
|
||||
|
||||
static CommandDispatcher<? extends Object> getDispatcher() {
|
||||
return ((CraftServer) Bukkit.getServer()).getServer().getCommandDispatcher().a();
|
||||
}
|
||||
}
|
32
SpigotCore_15/src/de/steamwar/command/Dispatcher_15.java
Normale Datei
32
SpigotCore_15/src/de/steamwar/command/Dispatcher_15.java
Normale Datei
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 com.mojang.brigadier.CommandDispatcher;
|
||||
import net.minecraft.server.v1_15_R1.CommandListenerWrapper;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_15_R1.CraftServer;
|
||||
|
||||
public class Dispatcher_15 {
|
||||
|
||||
static CommandDispatcher<CommandListenerWrapper> getDispatcher() {
|
||||
return ((CraftServer) Bukkit.getServer()).getServer().getCommandDispatcher().a();
|
||||
}
|
||||
}
|
@ -19,6 +19,11 @@
|
||||
<id>codemc-snapshots</id>
|
||||
<url>https://repo.codemc.io/repository/maven-snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>minecraft-libraries</id>
|
||||
<name>Minecraft Libraries</name>
|
||||
<url>https://libraries.minecraft.net</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<artifactId>SpigotCore_Main</artifactId>
|
||||
@ -124,5 +129,10 @@
|
||||
<version>2.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>brigadier</artifactId>
|
||||
<version>1.0.17</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
157
SpigotCore_Main/src/de/steamwar/command/CommandNode.java
Normale Datei
157
SpigotCore_Main/src/de/steamwar/command/CommandNode.java
Normale Datei
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.Array;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class CommandNode {
|
||||
|
||||
private final SWCommand swCommand;
|
||||
private final TypeMapper<?> typeMapper;
|
||||
private boolean varArg = false;
|
||||
private List<CommandNode> commandNodeList = new ArrayList<>();
|
||||
|
||||
private Method executor;
|
||||
private List<String> helpMessages = new ArrayList<>();
|
||||
|
||||
private Predicate<CommandSender> commandSenderPredicate;
|
||||
private Function<CommandSender, Object> commandSenderObjectFunction;
|
||||
|
||||
CommandNode(SWCommand swCommand, TypeMapper<?> typeMapper) {
|
||||
this.swCommand = swCommand;
|
||||
this.typeMapper = typeMapper;
|
||||
}
|
||||
|
||||
public void addNode(CommandNode commandNode) {
|
||||
commandNodeList.add(commandNode);
|
||||
this.varArg = false;
|
||||
}
|
||||
|
||||
public void setExecutor(Method executor) {
|
||||
if (this.executor != null) return;
|
||||
this.executor = executor;
|
||||
|
||||
Parameter parameter = executor.getParameters()[0];
|
||||
commandSenderPredicate = parameter.getType()::isInstance;
|
||||
commandSenderObjectFunction = parameter.getType()::cast;
|
||||
}
|
||||
|
||||
public void addHelpMessage(String helpMessage) {
|
||||
this.helpMessages.add(helpMessage);
|
||||
}
|
||||
|
||||
public void setVarArg(boolean varArg) {
|
||||
if (commandNodeList.isEmpty()) {
|
||||
this.varArg = varArg;
|
||||
} else {
|
||||
this.varArg = false;
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> tabComplete(CommandSender commandSender, int index, String[] args) {
|
||||
return internalTabCompleteAndSuggest((commandSender1, integer, strings) -> {
|
||||
return typeMapper.tabCompletes(commandSender1, Arrays.copyOf(strings, integer), strings[strings.length - 1]);
|
||||
}, commandSender, index, args);
|
||||
}
|
||||
|
||||
public boolean execute(CommandSender commandSender, int index, String[] args, List<Object> mappedObjects) {
|
||||
try {
|
||||
Object o;
|
||||
if (varArg) {
|
||||
o = Array.newInstance(Object.class, args.length - index);
|
||||
for (int i = 0; i < Array.getLength(o); i++) {
|
||||
Object current = typeMapper.map(commandSender, Arrays.copyOf(args, index + i), args[index + i]);
|
||||
if (current == null) return false;
|
||||
Array.set(o, i, current);
|
||||
}
|
||||
} else {
|
||||
o = typeMapper.map(commandSender, Arrays.copyOf(args, index), args[index]);
|
||||
if (o == null) return false;
|
||||
}
|
||||
mappedObjects.add(o);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index == args.length - 1) {
|
||||
if (executor == null) {
|
||||
return false;
|
||||
}
|
||||
if (!commandSenderPredicate.test(commandSender)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Object> finalMappedObjects = new ArrayList<>(mappedObjects);
|
||||
finalMappedObjects.add(0, commandSenderObjectFunction.apply(commandSender));
|
||||
|
||||
Object[] objects = finalMappedObjects.toArray(new Object[0]);
|
||||
try {
|
||||
executor.invoke(swCommand, objects);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
throw new SecurityException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return commandNodeList.stream()
|
||||
.map(commandNode -> commandNode.execute(commandSender, index + 1, args, new ArrayList<>(mappedObjects)))
|
||||
.findFirst().orElse(false);
|
||||
}
|
||||
|
||||
public List<String> suggest(CommandSender commandSender, int index, String[] args) {
|
||||
return internalTabCompleteAndSuggest((commandSender1, integer, strings) -> helpMessages, commandSender, index, args);
|
||||
}
|
||||
|
||||
private List<String> internalTabCompleteAndSuggest(TriFunction<CommandSender, Integer, String[], List<String>> returnFunction, CommandSender commandSender, int index, String[] args) {
|
||||
try {
|
||||
if (index == args.length - 1) {
|
||||
return returnFunction.apply(commandSender, index, args);
|
||||
}
|
||||
if (typeMapper.map(commandSender, Arrays.copyOf(args, index), args[index]) == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (varArg) {
|
||||
return internalTabCompleteAndSuggest(returnFunction, commandSender, index + 1, args);
|
||||
} else {
|
||||
return commandNodeList.stream()
|
||||
.map(commandNode -> commandNode.internalTabCompleteAndSuggest(returnFunction, commandSender, index + 1, args))
|
||||
.flatMap(List::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private interface TriFunction<I1, I2, I3, O> {
|
||||
O apply(I1 i1, I2 i2, I3 i3);
|
||||
}
|
||||
}
|
31
SpigotCore_Main/src/de/steamwar/command/CommandPart.java
Normale Datei
31
SpigotCore_Main/src/de/steamwar/command/CommandPart.java
Normale Datei
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
public class CommandPart {
|
||||
|
||||
public CommandPart(SWCommand swCommand, Method method, String[] subCommand, Map<String, TypeMapper<?>> localTypeMapper) {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -19,151 +19,50 @@
|
||||
|
||||
package de.steamwar.command;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import de.steamwar.core.VersionedCallable;
|
||||
|
||||
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;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class SWCommand {
|
||||
|
||||
private final Command command;
|
||||
private final List<SubCommand> commandList = new ArrayList<>();
|
||||
private final List<SubCommand> commandHelpList = new ArrayList<>();
|
||||
private final Map<String, TypeMapper<?>> localTypeMapper = new HashMap<>();
|
||||
static final CommandDispatcher<? extends Object> dispatcher;
|
||||
|
||||
protected SWCommand(String command) {
|
||||
this(command, new String[0]);
|
||||
static {
|
||||
dispatcher = VersionedCallable.call(new VersionedCallable<>(() -> null, 12),
|
||||
new VersionedCallable<>(Dispatcher_14::getDispatcher, 14),
|
||||
new VersionedCallable<>(Dispatcher_15::getDispatcher, 15));
|
||||
}
|
||||
|
||||
private SWCommandInterface swCommandInterface;
|
||||
|
||||
protected SWCommand(String command, boolean noBrigadier) {
|
||||
this(command, noBrigadier, 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) {
|
||||
if (commandList.stream().anyMatch(s -> s.invoke(sender, args))) return false;
|
||||
commandHelpList.stream().anyMatch(s -> s.invoke(sender, args));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
|
||||
String string = args[args.length - 1].toLowerCase();
|
||||
return commandList.stream()
|
||||
.map(s -> s.tabComplete(sender, args))
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(Collection::stream)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.filter(s -> s.toLowerCase().startsWith(string))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
};
|
||||
unregister();
|
||||
register();
|
||||
|
||||
Method[] methods = getClass().getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
addMapper(Mapper.class, method, i -> i == 0, false, TypeMapper.class, (anno, typeMapper) -> {
|
||||
(anno.local() ? localTypeMapper : SWCommandUtils.MAPPER_FUNCTIONS).putIfAbsent(anno.value(), typeMapper);
|
||||
});
|
||||
addMapper(ClassMapper.class, method, i -> i == 0, false, TypeMapper.class, (anno, typeMapper) -> {
|
||||
(anno.local() ? localTypeMapper : SWCommandUtils.MAPPER_FUNCTIONS).putIfAbsent(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;
|
||||
}
|
||||
commandHelpList.add(new SubCommand(this, method, anno.value(), new HashMap<>()));
|
||||
});
|
||||
}
|
||||
for (Method method : methods) {
|
||||
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 = mapper != null ? mapper.value() : clazz.getTypeName();
|
||||
if (!SWCommandUtils.MAPPER_FUNCTIONS.containsKey(name) && !localTypeMapper.containsKey(name)) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "The parameter '" + parameter.toString() + "' is using an unsupported Mapper of type '" + name + "'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
commandList.add(new SubCommand(this, method, anno.value(), localTypeMapper));
|
||||
});
|
||||
|
||||
this.commandList.sort((o1, o2) -> {
|
||||
int compare = Integer.compare(-o1.subCommand.length, -o2.subCommand.length);
|
||||
if (compare != 0) {
|
||||
return compare;
|
||||
} else {
|
||||
return Integer.compare(o1.varArgType != null ? Integer.MAX_VALUE : o1.arguments.length,
|
||||
o2.varArgType != null ? Integer.MAX_VALUE : o2.arguments.length);
|
||||
}
|
||||
});
|
||||
commandHelpList.sort(Comparator.comparingInt(o -> -o.subCommand.length));
|
||||
}
|
||||
this(command, false, aliases);
|
||||
}
|
||||
|
||||
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 || anno.length == 0) 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;
|
||||
protected SWCommand(String command, boolean noBrigadier, String... aliases) {
|
||||
if (dispatcher != null && !noBrigadier) {
|
||||
swCommandInterface = new SWCommandBrigadier(this, command, aliases);
|
||||
} else {
|
||||
swCommandInterface = new SWCommandNormal(this, command, aliases);
|
||||
}
|
||||
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;
|
||||
}
|
||||
Arrays.stream(anno).forEach(t -> consumer.accept(t, 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);
|
||||
consumer.accept(anno, (TypeMapper<?>) method.invoke(this));
|
||||
} catch (Exception e) {
|
||||
throw new SecurityException(e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
SWCommandUtils.knownCommandMap.remove(command.getName());
|
||||
command.getAliases().forEach(SWCommandUtils.knownCommandMap::remove);
|
||||
command.unregister(SWCommandUtils.commandMap);
|
||||
swCommandInterface.unregister();
|
||||
}
|
||||
|
||||
public void register() {
|
||||
SWCommandUtils.commandMap.register("steamwar", this.command);
|
||||
swCommandInterface.register();
|
||||
}
|
||||
|
||||
interface SWCommandInterface {
|
||||
void unregister();
|
||||
void register();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@ -196,4 +95,34 @@ public abstract class SWCommand {
|
||||
|
||||
boolean local() default false;
|
||||
}
|
||||
|
||||
|
||||
// Used for Brigadier, as parse hints and TabComplete hints
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
protected @interface IntRange {
|
||||
int min();
|
||||
int max();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
protected @interface LongRange {
|
||||
long min();
|
||||
long max();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
protected @interface FloatRange {
|
||||
float min();
|
||||
float max();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.PARAMETER})
|
||||
protected @interface DoubleRange {
|
||||
double min();
|
||||
double max();
|
||||
}
|
||||
}
|
||||
|
213
SpigotCore_Main/src/de/steamwar/command/SWCommandBrigadier.java
Normale Datei
213
SpigotCore_Main/src/de/steamwar/command/SWCommandBrigadier.java
Normale Datei
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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 com.mojang.brigadier.arguments.*;
|
||||
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import net.minecraft.server.v1_15_R1.CommandListenerWrapper;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
class SWCommandBrigadier implements SWCommand.SWCommandInterface {
|
||||
|
||||
private final String command;
|
||||
private final String[] aliases;
|
||||
|
||||
private final SWCommand swCommand;
|
||||
private final List<SubCommand> commandList = new ArrayList<>();
|
||||
private final List<SubCommand> commandHelpList = new ArrayList<>();
|
||||
private final Map<String, TypeMapper<?>> localTypeMapper = new HashMap<>();
|
||||
|
||||
protected SWCommandBrigadier(SWCommand swCommand, String command, String... aliases) {
|
||||
this.command = command;
|
||||
this.aliases = aliases;
|
||||
|
||||
this.swCommand = swCommand;
|
||||
SWCommandUtils.createList(swCommand, commandList, commandHelpList, localTypeMapper, this::createSubCommand);
|
||||
Stream.of(commandList, commandHelpList).flatMap(List::stream).forEach(subCommand -> {
|
||||
register(command, subCommand);
|
||||
for (String s : aliases) {
|
||||
register(s, subCommand);
|
||||
}
|
||||
});
|
||||
// unregister();
|
||||
// register();
|
||||
}
|
||||
|
||||
private SubCommand createSubCommand(Method method, String[] strings) {
|
||||
return new SubCommand(swCommand, method, strings, localTypeMapper, current -> {
|
||||
List<ArgumentBuilder> argumentBuilders = new ArrayList<>();
|
||||
|
||||
for (String s : current.subCommand) {
|
||||
LiteralArgumentBuilder literalArgumentBuilder = LiteralArgumentBuilder.literal(s);
|
||||
List<ArgumentBuilder> currentBuilders = new ArrayList<>();
|
||||
argumentBuilders.forEach(currrentBuilder -> {
|
||||
currentBuilders.add(currrentBuilder.then(literalArgumentBuilder));
|
||||
});
|
||||
if (argumentBuilders.isEmpty()) {
|
||||
currentBuilders.add(literalArgumentBuilder);
|
||||
if (current.argumentNode.isEmpty()) {
|
||||
current.argumentNode.add(literalArgumentBuilder);
|
||||
}
|
||||
}
|
||||
argumentBuilders = currentBuilders;
|
||||
}
|
||||
|
||||
for (int i = 0; i < current.arguments.length - (current.varArgType != null ? 1 : 0); i++) {
|
||||
Parameter parameter = current.parameters[i + 1];
|
||||
Class<?> parameterType = parameter.getType();
|
||||
ArgumentType<?> argumentType = getArgumentType(parameter, parameterType);
|
||||
List<ArgumentBuilder> newBuilders = new ArrayList<>();
|
||||
if (argumentType == null) {
|
||||
try {
|
||||
current.arguments[i].tabCompletes(null, null, "").stream()
|
||||
.map(LiteralArgumentBuilder::literal)
|
||||
.forEach(newBuilders::add);
|
||||
} catch (Exception e) {
|
||||
newBuilders.add(RequiredArgumentBuilder.argument(parameter.getName(), StringArgumentType.string()));
|
||||
}
|
||||
} else {
|
||||
newBuilders.add(RequiredArgumentBuilder.argument(parameter.getName(), argumentType));
|
||||
}
|
||||
|
||||
List<ArgumentBuilder> currentBuilders = new ArrayList<>();
|
||||
argumentBuilders.forEach(currrentBuilder -> {
|
||||
newBuilders.forEach(argumentBuilder -> {
|
||||
currentBuilders.add(currrentBuilder.then(argumentBuilder));
|
||||
});
|
||||
});
|
||||
if (argumentBuilders.isEmpty()) {
|
||||
currentBuilders.addAll(newBuilders);
|
||||
if (current.argumentNode.isEmpty()) {
|
||||
current.argumentNode.addAll(newBuilders);
|
||||
}
|
||||
}
|
||||
argumentBuilders = currentBuilders;
|
||||
}
|
||||
if (current.varArgType != null) {
|
||||
Parameter parameter = current.parameters[current.parameters.length - 1];
|
||||
Class<?> parameterType = parameter.getType();
|
||||
ArgumentType<?> argumentType = getArgumentType(parameter, parameterType);
|
||||
List<ArgumentBuilder> newBuilders = new ArrayList<>();
|
||||
if (argumentType == null) {
|
||||
try {
|
||||
current.arguments[current.arguments.length - 1].tabCompletes(null, null, "").stream()
|
||||
.map(LiteralArgumentBuilder::literal)
|
||||
.forEach(newBuilders::add);
|
||||
} catch (Exception e) {
|
||||
newBuilders.add(RequiredArgumentBuilder.argument(parameter.getName(), StringArgumentType.string()));
|
||||
}
|
||||
} else {
|
||||
newBuilders.add(RequiredArgumentBuilder.argument(parameter.getName(), argumentType));
|
||||
}
|
||||
|
||||
// TODO: VarArgs
|
||||
List<ArgumentBuilder> currentBuilders = new ArrayList<>();
|
||||
argumentBuilders.forEach(currrentBuilder -> {
|
||||
newBuilders.forEach(argumentBuilder -> {
|
||||
currentBuilders.add(currrentBuilder.then(argumentBuilder));
|
||||
});
|
||||
});
|
||||
if (argumentBuilders.isEmpty()) {
|
||||
currentBuilders.addAll(newBuilders);
|
||||
if (current.argumentNode.isEmpty()) {
|
||||
current.argumentNode.addAll(newBuilders);
|
||||
}
|
||||
}
|
||||
currentBuilders.forEach(argumentBuilder -> argumentBuilder.executes(executes(current)));
|
||||
argumentBuilders = currentBuilders;
|
||||
}
|
||||
current.argumentNodeEnd.addAll(argumentBuilders);
|
||||
});
|
||||
}
|
||||
|
||||
private com.mojang.brigadier.Command<?> executes(SubCommand current) {
|
||||
return commandContext -> {
|
||||
CommandSender commandSender = ((CommandListenerWrapper) commandContext.getSource()).getBukkitSender();
|
||||
List<String> stringList = Arrays.stream(commandContext.getInput().split(" ")).collect(Collectors.toList());
|
||||
stringList.remove(0);
|
||||
String[] args = stringList.toArray(new String[0]);
|
||||
current.invoke(commandSender, args);
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
private void register(String name, SubCommand subCommand) {
|
||||
LiteralArgumentBuilder literalArgumentBuilder = LiteralArgumentBuilder.literal(name);
|
||||
if (subCommand.argumentNode == null) {
|
||||
literalArgumentBuilder.executes(executes(subCommand));
|
||||
SWCommand.dispatcher.register(literalArgumentBuilder);
|
||||
return;
|
||||
}
|
||||
subCommand.argumentNodeEnd.forEach(argumentBuilder -> argumentBuilder.executes(executes(subCommand)));
|
||||
subCommand.argumentNode.forEach(literalArgumentBuilder::then);
|
||||
SWCommand.dispatcher.register(literalArgumentBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
SWCommandUtils.knownCommandMap.remove(command);
|
||||
Arrays.stream(aliases).forEach(SWCommandUtils.knownCommandMap::remove);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register() {
|
||||
Stream.of(commandList, commandHelpList).flatMap(List::stream).forEach(subCommand -> {
|
||||
register(command, subCommand);
|
||||
for (String s : aliases) {
|
||||
register(s, subCommand);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ArgumentType<?> getArgumentType(Parameter parameter, Class<?> parameterType) {
|
||||
ArgumentType<?> argumentType;
|
||||
if (parameterType == boolean.class || parameterType == Boolean.class) {
|
||||
argumentType = BoolArgumentType.bool();
|
||||
} else if (parameterType == int.class || parameterType == Integer.class) {
|
||||
SWCommand.IntRange intRange = parameter.getAnnotation(SWCommand.IntRange.class);
|
||||
if (intRange != null) argumentType = IntegerArgumentType.integer(intRange.min(), intRange.max());
|
||||
else argumentType = IntegerArgumentType.integer();
|
||||
} else if (parameterType == float.class || parameterType == Float.class) {
|
||||
SWCommand.FloatRange floatRange = parameter.getAnnotation(SWCommand.FloatRange.class);
|
||||
if (floatRange != null) argumentType = FloatArgumentType.floatArg(floatRange.min(), floatRange.max());
|
||||
else argumentType = FloatArgumentType.floatArg();
|
||||
} else if (parameterType == long.class || parameterType == Long.class) {
|
||||
SWCommand.LongRange longRange = parameter.getAnnotation(SWCommand.LongRange.class);
|
||||
if (longRange != null) argumentType = LongArgumentType.longArg(longRange.min(), longRange.max());
|
||||
else argumentType = LongArgumentType.longArg();
|
||||
} else if (parameterType == double.class || parameterType == Double.class) {
|
||||
SWCommand.DoubleRange doubleRange = parameter.getAnnotation(SWCommand.DoubleRange.class);
|
||||
if (doubleRange != null) argumentType = DoubleArgumentType.doubleArg(doubleRange.min(), doubleRange.max());
|
||||
else argumentType = DoubleArgumentType.doubleArg();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return argumentType;
|
||||
}
|
||||
}
|
84
SpigotCore_Main/src/de/steamwar/command/SWCommandNormal.java
Normale Datei
84
SpigotCore_Main/src/de/steamwar/command/SWCommandNormal.java
Normale Datei
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.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;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class SWCommandNormal implements SWCommand.SWCommandInterface {
|
||||
|
||||
private final SWCommand swCommand;
|
||||
private final Command command;
|
||||
private final List<SubCommand> commandList = new ArrayList<>();
|
||||
private final List<SubCommand> commandHelpList = new ArrayList<>();
|
||||
private final Map<String, TypeMapper<?>> localTypeMapper = new HashMap<>();
|
||||
|
||||
protected SWCommandNormal(SWCommand swCommand, String command, String... aliases) {
|
||||
this.swCommand = swCommand;
|
||||
|
||||
this.command = new Command(command, "", "/" + command, Arrays.asList(aliases)) {
|
||||
@Override
|
||||
public boolean execute(CommandSender sender, String alias, String[] args) {
|
||||
if (commandList.stream().anyMatch(s -> s.invoke(sender, args))) return false;
|
||||
commandHelpList.stream().anyMatch(s -> s.invoke(sender, args));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
|
||||
String string = args[args.length - 1].toLowerCase();
|
||||
return commandList.stream()
|
||||
.map(s -> s.tabComplete(sender, args))
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(Collection::stream)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.filter(s -> s.toLowerCase().startsWith(string))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
};
|
||||
unregister();
|
||||
register();
|
||||
SWCommandUtils.createList(swCommand, commandList, commandHelpList, localTypeMapper, this::createSubCommand);
|
||||
}
|
||||
|
||||
private SubCommand createSubCommand(Method method, String[] strings) {
|
||||
return new de.steamwar.command.SubCommand(swCommand, method, strings, localTypeMapper, current -> {});
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
SWCommandUtils.knownCommandMap.remove(command.getName());
|
||||
command.getAliases().forEach(SWCommandUtils.knownCommandMap::remove);
|
||||
command.unregister(SWCommandUtils.commandMap);
|
||||
}
|
||||
|
||||
public void register() {
|
||||
SWCommandUtils.commandMap.register("steamwar", this.command);
|
||||
}
|
||||
}
|
@ -32,9 +32,13 @@ import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntPredicate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SWCommandUtils {
|
||||
@ -180,4 +184,83 @@ public class SWCommandUtils {
|
||||
if (method.getAnnotations().length != 1) return null;
|
||||
return method.getDeclaredAnnotationsByType(annotation);
|
||||
}
|
||||
|
||||
static void createList(SWCommand swCommand, List<SubCommand> commandList, List<SubCommand> commandHelpList, Map<String, TypeMapper<?>> localTypeMapper, BiFunction<Method, String[], SubCommand> creator) {
|
||||
Method[] methods = swCommand.getClass().getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
addMapper(swCommand, SWCommand.Mapper.class, method, i -> i == 0, false, TypeMapper.class, (anno, typeMapper) -> {
|
||||
(anno.local() ? localTypeMapper : SWCommandUtils.MAPPER_FUNCTIONS).putIfAbsent(anno.value(), typeMapper);
|
||||
});
|
||||
addMapper(swCommand, SWCommand.ClassMapper.class, method, i -> i == 0, false, TypeMapper.class, (anno, typeMapper) -> {
|
||||
(anno.local() ? localTypeMapper : SWCommandUtils.MAPPER_FUNCTIONS).putIfAbsent(anno.value().getTypeName(), typeMapper);
|
||||
});
|
||||
add(SWCommand.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;
|
||||
}
|
||||
commandHelpList.add(creator.apply(method, anno.value()));
|
||||
});
|
||||
}
|
||||
for (Method method : methods) {
|
||||
add(SWCommand.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();
|
||||
}
|
||||
SWCommand.Mapper mapper = parameter.getAnnotation(SWCommand.Mapper.class);
|
||||
if (clazz.isEnum() && mapper == null && !SWCommandUtils.MAPPER_FUNCTIONS.containsKey(clazz.getTypeName())) {
|
||||
continue;
|
||||
}
|
||||
String name = mapper != null ? mapper.value() : clazz.getTypeName();
|
||||
if (!SWCommandUtils.MAPPER_FUNCTIONS.containsKey(name) && !localTypeMapper.containsKey(name)) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "The parameter '" + parameter + "' is using an unsupported Mapper of type '" + name + "'");
|
||||
return;
|
||||
}
|
||||
}
|
||||
commandList.add(creator.apply(method, anno.value()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static <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 || anno.length == 0) return;
|
||||
|
||||
Parameter[] parameters = method.getParameters();
|
||||
if (!parameterTester.test(parameters.length)) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "The method '" + method + "' is lacking parameters or has too many");
|
||||
return;
|
||||
}
|
||||
if (firstParameter && !CommandSender.class.isAssignableFrom(parameters[0].getType())) {
|
||||
Bukkit.getLogger().log(Level.WARNING, "The method '" + method + "' 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 + "' is lacking the desired return type '" + returnType.getTypeName() + "'");
|
||||
return;
|
||||
}
|
||||
Arrays.stream(anno).forEach(t -> consumer.accept(t, parameters));
|
||||
}
|
||||
|
||||
private static <T extends Annotation> void addMapper(SWCommand swCommand, 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);
|
||||
consumer.accept(anno, (TypeMapper<?>) method.invoke(swCommand));
|
||||
} catch (Exception e) {
|
||||
throw new SecurityException(e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -19,12 +19,14 @@
|
||||
|
||||
package de.steamwar.command;
|
||||
|
||||
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||
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.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@ -32,19 +34,23 @@ import static de.steamwar.command.SWCommandUtils.*;
|
||||
|
||||
class SubCommand {
|
||||
|
||||
private SWCommand swCommand;
|
||||
private Method method;
|
||||
List<ArgumentBuilder> argumentNode = new ArrayList<>();
|
||||
List<ArgumentBuilder> argumentNodeEnd = new ArrayList<>();
|
||||
|
||||
SWCommand swCommand;
|
||||
Parameter[] parameters;
|
||||
Method method;
|
||||
String[] subCommand;
|
||||
TypeMapper<?>[] arguments;
|
||||
private Predicate<CommandSender> commandSenderPredicate;
|
||||
private Function<CommandSender, ?> commandSenderFunction;
|
||||
Predicate<CommandSender> commandSenderPredicate;
|
||||
Function<CommandSender, ?> commandSenderFunction;
|
||||
Class<?> varArgType = null;
|
||||
|
||||
SubCommand(SWCommand swCommand, Method method, String[] subCommand, Map<String, TypeMapper<?>> localTypeMapper) {
|
||||
public SubCommand(SWCommand swCommand, Method method, String[] subCommand, Map<String, TypeMapper<?>> localTypeMapper, Consumer<SubCommand> consumer) {
|
||||
this.swCommand = swCommand;
|
||||
this.method = method;
|
||||
|
||||
Parameter[] parameters = method.getParameters();
|
||||
parameters = method.getParameters();
|
||||
commandSenderPredicate = sender -> parameters[0].getType().isAssignableFrom(sender.getClass());
|
||||
commandSenderFunction = sender -> parameters[0].getType().cast(sender);
|
||||
this.subCommand = subCommand;
|
||||
@ -77,6 +83,8 @@ class SubCommand {
|
||||
? localTypeMapper.get(name)
|
||||
: MAPPER_FUNCTIONS.getOrDefault(name, ERROR_FUNCTION);
|
||||
}
|
||||
|
||||
consumer.accept(this);
|
||||
}
|
||||
|
||||
boolean invoke(CommandSender commandSender, String[] args) {
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren