diff --git a/SpigotCore_Main/src/de/steamwar/command/InternalCommand.java b/SpigotCore_Main/src/de/steamwar/command/InternalCommand.java
new file mode 100644
index 0000000..bf0d759
--- /dev/null
+++ b/SpigotCore_Main/src/de/steamwar/command/InternalCommand.java
@@ -0,0 +1,54 @@
+/*
+ * 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 .
+ */
+
+package de.steamwar.command;
+
+import org.bukkit.command.CommandSender;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+
+class InternalCommand {
+
+ private SWCommand swCommand;
+ private Method method;
+ private Parameter[] parameters;
+
+ InternalCommand(SWCommand swCommand, Method method) {
+ this.swCommand = swCommand;
+ this.method = method;
+ parameters = method.getParameters();
+ }
+
+ boolean invoke(CommandSender commandSender, String[] args) {
+ if (args.length < parameters.length - 1) return false;
+ Object[] objects = SWCommandUtils.generateArgumentArray(commandSender, parameters, args);
+ try {
+ method.setAccessible(true);
+ method.invoke(swCommand, objects);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ throw new SecurityException(e.getMessage(), e);
+ } catch (InvocationTargetException e) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/SpigotCore_Main/src/de/steamwar/command/InternalTabComplete.java b/SpigotCore_Main/src/de/steamwar/command/InternalTabComplete.java
new file mode 100644
index 0000000..b6f0d47
--- /dev/null
+++ b/SpigotCore_Main/src/de/steamwar/command/InternalTabComplete.java
@@ -0,0 +1,53 @@
+/*
+ * 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 .
+ */
+
+package de.steamwar.command;
+
+import org.bukkit.command.CommandSender;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+
+class InternalTabComplete {
+
+ private SWCommand swCommand;
+ private Method method;
+ private Parameter[] parameters;
+
+ InternalTabComplete(SWCommand swCommand, Method method) {
+ this.swCommand = swCommand;
+ this.method = method;
+ parameters = method.getParameters();
+ }
+
+ SWCommandUtils.TabComplete invoke(CommandSender commandSender, String[] args) {
+ if (args.length < parameters.length - 1) return null;
+ Object[] objects = SWCommandUtils.generateArgumentArray(commandSender, parameters, args);
+ try {
+ method.setAccessible(true);
+ return (SWCommandUtils.TabComplete) method.invoke(swCommand, objects);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ throw new SecurityException(e.getMessage(), e);
+ } catch (InvocationTargetException e) {
+ return null;
+ }
+ }
+
+}
diff --git a/SpigotCore_Main/src/de/steamwar/command/SWCommand.java b/SpigotCore_Main/src/de/steamwar/command/SWCommand.java
new file mode 100644
index 0000000..725b891
--- /dev/null
+++ b/SpigotCore_Main/src/de/steamwar/command/SWCommand.java
@@ -0,0 +1,102 @@
+/*
+ * 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 .
+ */
+
+package de.steamwar.command;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.*;
+import java.util.function.Consumer;
+
+public abstract class SWCommand {
+
+ private final Set commandSet = new HashSet<>();
+ private final Set tabCompleteSet = new HashSet<>();
+ private Consumer helpMessage = sender -> {};
+
+ protected SWCommand(String command, String... aliases) {
+ SWCommandUtils.commandMap.register("steamwar", new Command(command, "", "/" + command, Arrays.asList(aliases)) {
+ @Override
+ public boolean execute(CommandSender sender, String alias, String[] args) {
+ for (InternalCommand internalCommand : commandSet) {
+ if (internalCommand.invoke(sender, args)) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
+ List strings = new ArrayList<>();
+ for (InternalTabComplete internalTabComplete : tabCompleteSet) {
+ SWCommandUtils.TabComplete tabComplete = internalTabComplete.invoke(sender, args);
+ if (tabComplete != null) {
+ strings.addAll(tabComplete.tabCompletes);
+ }
+ }
+ return strings;
+ }
+ });
+ }
+
+ protected final void register(String methodName) {
+ if (methodName.equals("onCommand")) return;
+ if (methodName.equals("onTabComplete")) return;
+ Method[] methods = this.getClass().getDeclaredMethods();
+ for (Method method : methods) {
+ if (!validMethod(method)) continue;
+ if (method.getReturnType() == Void.TYPE) {
+ commandSet.add(new InternalCommand(this, method));
+ }
+ if (method.getReturnType() == SWCommandUtils.TabComplete.class) {
+ tabCompleteSet.add(new InternalTabComplete(this, method));
+ }
+ }
+ }
+
+ private boolean validMethod(Method method) {
+ Parameter[] parameters = method.getParameters();
+ if (parameters.length == 0) return false;
+ if (!CommandSender.class.isAssignableFrom(parameters[0].getType())) return false;
+ 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();
+ }
+ if (clazz.isEnum()) continue;
+ if (!SWCommandUtils.MAPPER_FUNCTIONS.containsKey(clazz)) return false;
+ }
+ return true;
+ }
+
+ protected final void setHelpMessage(Consumer helpMessage) {
+ assert helpMessage != null;
+ this.helpMessage = helpMessage;
+ }
+
+ protected final void sendHelpMessage(T sender) {
+ helpMessage.accept(sender);
+ }
+
+}
diff --git a/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java b/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java
new file mode 100644
index 0000000..aaa194e
--- /dev/null
+++ b/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java
@@ -0,0 +1,130 @@
+/*
+ * 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 .
+ */
+
+package de.steamwar.command;
+
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandMap;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Parameter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+class SWCommandUtils {
+
+ private SWCommandUtils() {
+ throw new IllegalStateException("Utility Class");
+ }
+
+ static final Map, Function> MAPPER_FUNCTIONS = new HashMap<>();
+
+ static final Function ERROR_FUNCTION = s -> {
+ throw new SecurityException(s);
+ };
+
+ static final BiFunction>, String, Enum>> ENUM_MAPPER = (enumClass, s) -> {
+ Enum>[] enums = enumClass.getEnumConstants();
+ for (Enum> e : enums) {
+ if (e.name().equals(s)) return e;
+ }
+ return null;
+ };
+
+ static {
+ MAPPER_FUNCTIONS.put(boolean.class, Boolean::parseBoolean);
+ MAPPER_FUNCTIONS.put(Boolean.class, Boolean::parseBoolean);
+ MAPPER_FUNCTIONS.put(float.class, Float::parseFloat);
+ MAPPER_FUNCTIONS.put(Float.class, Float::parseFloat);
+ MAPPER_FUNCTIONS.put(double.class, Double::parseDouble);
+ MAPPER_FUNCTIONS.put(Double.class, Double::parseDouble);
+ MAPPER_FUNCTIONS.put(int.class, Integer::parseInt);
+ MAPPER_FUNCTIONS.put(Integer.class, Integer::parseInt);
+ MAPPER_FUNCTIONS.put(String.class, s -> s);
+ MAPPER_FUNCTIONS.put(StringBuilder.class, StringBuilder::new);
+ MAPPER_FUNCTIONS.put(Player.class, Bukkit::getPlayer);
+ MAPPER_FUNCTIONS.put(UUID.class, UUID::fromString);
+ }
+
+ static final CommandMap commandMap;
+
+ 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 register.", exception);
+ }
+ }
+
+ public static Object[] generateArgumentArray(CommandSender commandSender, Parameter[] parameters, String[] args) {
+ Object[] arguments = new Object[args.length + 1];
+ boolean varArgs = false;
+ if (parameters[parameters.length - 1].isVarArgs()) {
+ varArgs = true;
+ arguments = new Object[parameters.length];
+ }
+ arguments[0] = parameters[0].getType().cast(commandSender);
+
+ for (int i = 1; i < parameters.length - (varArgs ? 1 : 0); i++) {
+ Class> clazz = parameters[i].getType();
+ arguments[i] = mapper(clazz).apply(args[i - 1]);
+ }
+
+ if (varArgs) {
+ Object[] varArgument = new Object[args.length - parameters.length + 2];
+ arguments[arguments.length - 1] = varArgument;
+ Function mapper = mapper(parameters[parameters.length - 1].getType().getComponentType());
+
+ int index = 0;
+ for (int i = parameters.length - 2; i < args.length; i++) {
+ varArgument[index++] = mapper.apply(args[i]);
+ }
+ }
+
+ return arguments;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Function mapper(Class> clazz) {
+ if (clazz.isEnum()) {
+ Class> enumClass = (Class>) clazz;
+ return s -> ENUM_MAPPER.apply(enumClass, s);
+ } else {
+ return s -> MAPPER_FUNCTIONS.getOrDefault(clazz, ERROR_FUNCTION).apply(s);
+ }
+ }
+
+ public static class TabComplete {
+ final List tabCompletes;
+
+ public TabComplete(List tabCompletes) {
+ this.tabCompletes = tabCompletes;
+ }
+ }
+
+}