diff --git a/SpigotCore_14/src/de/steamwar/core/FlatteningWrapper14.java b/SpigotCore_14/src/de/steamwar/core/FlatteningWrapper14.java index 3a28106..2092669 100644 --- a/SpigotCore_14/src/de/steamwar/core/FlatteningWrapper14.java +++ b/SpigotCore_14/src/de/steamwar/core/FlatteningWrapper14.java @@ -222,15 +222,15 @@ public class FlatteningWrapper14 implements FlatteningWrapper.IFlatteningWrapper renamedLegacy.put("RECORD_12", Material.MUSIC_DISC_WAIT); } - private static final Reflection.FieldAccessor scoreboardName = Reflection.getField(SWScoreboard.scoreboardObjective, Reflection.getClass("{nms}.IChatBaseComponent"), 0); - private static final Reflection.ConstructorInvoker chatComponentConstructor = Reflection.getConstructor(Reflection.getClass("{nms}.ChatComponentText"), String.class); + private static final Reflection.FieldAccessor scoreboardName = Reflection.getField(SWScoreboard.scoreboardObjective, Reflection.getClass("{nms.network.chat}.IChatBaseComponent"), 0); + private static final Reflection.ConstructorInvoker chatComponentConstructor = Reflection.getConstructor(Reflection.getClass("{nms.network.chat}.ChatComponentText"), String.class); @Override public void setScoreboardTitle(Object packet, String title) { scoreboardName.set(packet, chatComponentConstructor.invoke(title)); } - private static final Class scoreActionEnum = Reflection.getClass("{nms}.ScoreboardServer$Action"); + private static final Class scoreActionEnum = Reflection.getClass("{nms.server}.ScoreboardServer$Action"); private static final Reflection.FieldAccessor scoreAction = Reflection.getField(SWScoreboard.scoreboardScore, scoreActionEnum, 0); private static final Object scoreActionChange = scoreActionEnum.getEnumConstants()[0]; diff --git a/SpigotCore_18/build.gradle b/SpigotCore_18/build.gradle new file mode 100644 index 0000000..c6185e9 --- /dev/null +++ b/SpigotCore_18/build.gradle @@ -0,0 +1,50 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2021 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 . + */ + +plugins { + id 'base' + id 'java' +} + +group 'steamwar' +version '1.0' + +compileJava.options.encoding = 'UTF-8' + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +sourceSets { + main { + java { + srcDirs = ['src/'] + } + resources { + srcDirs = ['src/'] + exclude '**/*.java', '**/*.kt' + } + } +} + +dependencies { + implementation project(":SpigotCore_Main") + + compileOnly 'org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT' + compileOnly files("${project.rootDir}/lib/Spigot-1.18.jar") +} diff --git a/SpigotCore_18/settings.gradle b/SpigotCore_18/settings.gradle new file mode 100644 index 0000000..8943e4f --- /dev/null +++ b/SpigotCore_18/settings.gradle @@ -0,0 +1,20 @@ +/* + * 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 . + */ + +rootProject.name = 'SpigotCore_18' \ No newline at end of file diff --git a/SpigotCore_18/src/de/steamwar/core/CraftbukkitWrapper18.java b/SpigotCore_18/src/de/steamwar/core/CraftbukkitWrapper18.java new file mode 100644 index 0000000..594fbac --- /dev/null +++ b/SpigotCore_18/src/de/steamwar/core/CraftbukkitWrapper18.java @@ -0,0 +1,42 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 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.core; + +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.chunk.Chunk; +import org.bukkit.craftbukkit.v1_18_R1.CraftChunk; +import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer; +import org.bukkit.entity.Player; + +public class CraftbukkitWrapper18 implements CraftbukkitWrapper.ICraftbukkitWrapper { + + @Override + public void sendChunk(Player p, int chunkX, int chunkZ) { + Chunk chunk = ((CraftChunk)p.getWorld().getChunkAt(chunkX, chunkZ)).getHandle(); + ((CraftPlayer)p).getHandle().b.a(new ClientboundLevelChunkWithLightPacket(chunk, chunk.q.l_(), null, null, false)); + } + + @Override + @SuppressWarnings("deprecation") + public double[] getSpigotTPS() { + return MinecraftServer.getServer().recentTps; + } +} diff --git a/SpigotCore_Main/build.gradle b/SpigotCore_Main/build.gradle index 7a48ea5..faeb028 100644 --- a/SpigotCore_Main/build.gradle +++ b/SpigotCore_Main/build.gradle @@ -40,17 +40,36 @@ sourceSets { exclude '**/*.java', '**/*.kt' } } + + test { + java { + srcDirs = ['testsrc'] + } + resources { + srcDirs = ['testsrc'] + exclude '**/*.java', '**/*.kt' + } + } } dependencies { - compileOnly files("${project.rootDir}/lib/Spigot-1.15.jar") + compileOnly 'org.spigotmc:spigot-api:1.18-R0.1-SNAPSHOT' + compileOnly 'io.netty:netty-all:4.1.68.Final' + compileOnly 'com.mojang:authlib:1.5.25' + compileOnly 'mysql:mysql-connector-java:5.1.49' compileOnly files("${project.rootDir}/lib/WorldEdit-1.12.jar") implementation 'net.wesjd:anvilgui:1.4.0-SNAPSHOT' - compileOnly 'org.projectlombok:lombok:1.18.6' - testCompileOnly 'org.projectlombok:lombok:1.18.6' - annotationProcessor 'org.projectlombok:lombok:1.18.6' - testAnnotationProcessor 'org.projectlombok:lombok:1.18.6' + compileOnly 'org.projectlombok:lombok:1.18.22' + testCompileOnly 'org.projectlombok:lombok:1.18.22' + annotationProcessor 'org.projectlombok:lombok:1.18.22' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.22' + + testImplementation files("${project.rootDir}/lib/Spigot-1.15.jar") + testImplementation files("${project.rootDir}/lib/WorldEdit-1.12.jar") + + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.hamcrest:hamcrest:2.2' } processResources { diff --git a/SpigotCore_Main/src/SpigotCore.properties b/SpigotCore_Main/src/SpigotCore.properties index e36969f..acb3749 100644 --- a/SpigotCore_Main/src/SpigotCore.properties +++ b/SpigotCore_Main/src/SpigotCore.properties @@ -17,39 +17,83 @@ # along with this program. If not, see . # -SCHEM_SELECTOR_TITLE={0} auswählen: {1} -SCHEM_SELECTOR_BACK=§eZurück -SCHEM_SELECTOR_DIR=§9Ordner -SCHEM_SELECTOR_RANK=§8Rang {0} -SCHEM_SELECTOR_OWN=§7Eigene Schematics -SCHEM_SELECTOR_PUB=§7Public Schematics -SCHEM_SELECTOR_SEL_DIR=§7Ordner auswählen -SCHEM_SELECTOR_NEW_DIR=§7Neuer Ordner -SCHEM_SELECTOR_FILTER=§7Filter +SCHEM_SELECTOR_TITLE={0} auswählen: {1} +SCHEM_SELECTOR_BACK=§eZurück +SCHEM_SELECTOR_DIR=§9Ordner +SCHEM_SELECTOR_RANK=§8Rang {0} +SCHEM_SELECTOR_OWN=§7Eigene Schematics +SCHEM_SELECTOR_PUB=§7Public Schematics +SCHEM_SELECTOR_SEL_DIR=§7Ordner auswählen +SCHEM_SELECTOR_NEW_DIR=§7Neuer Ordner +SCHEM_SELECTOR_FILTER=§7Filter +SCHEM_SELECTOR_SORTING=§7Sortierung +SCHEM_SELECTOR_SORTING_CURRENT=§7Aktuell: §e{0} +SCHEM_SELECTOR_SORTING_NAME=Name +SCHEM_SELECTOR_SORTING_TYPE=Schematic-Typ +SCHEM_SELECTOR_SORTING_UPDATE=Letztes Update +SCHEM_SELECTOR_SORTING_DIRECTION=§7Richtung: §e{0} +SCHEM_SELECTOR_SORTING_ASC=Aufsteigend +SCHEM_SELECTOR_SORTING_DSC=Absteigend -SCHEM_SELECTOR_ITEM_NAME=§e{0} -SCHEM_SELECTOR_ITEM_NAME_FILTER=§7{0} -SCHEM_SELECTOR_ITEM_REPLACE=§e{0}§7 -SCHEM_SELECTOR_ITEM_LORE_TYPE=§7{0} +SCHEM_SELECTOR_ITEM_NAME=§e{0} +SCHEM_SELECTOR_ITEM_NAME_FILTER=§7{0} +SCHEM_SELECTOR_ITEM_REPLACE=§e{0}§7 +SCHEM_SELECTOR_ITEM_LORE_TYPE=§7{0} SCHEM_SELECTOR_CREATE_DIR_TITLE=Ordner erstellen SCHEM_SELECTOR_FILTER_TITLE=Filter SCHEM_SELECTOR_FILTER_ENTER_NAME=Name eingeben -SCHEM_SELECTOR_FILTER_NAME=§7Nach Namen suchen... -SCHEM_SELECTOR_FILTER_NAME_SEARCH=§7Suchbegriff: §e{0} +SCHEM_SELECTOR_FILTER_NAME=§7Nach Namen suchen... +SCHEM_SELECTOR_FILTER_NAME_SEARCH=§7Suchbegriff: §e{0} SCHEM_SELECTOR_FILTER_ENTER_OWNER=Besitzer eingeben -SCHEM_SELECTOR_FILTER_OWNER=§7Nach Besitzer suchen... -SCHEM_SELECTOR_FILTER_OWNER_SEARCH=§7Besitzer: §e{0} -SCHEM_SELECTOR_FILTER_SEL_TYPE=Typ wählen... -SCHEM_SELECTOR_FILTER_TYPE=§7Nach Typ filtern... -SCHEM_SELECTOR_FILTER_TYPE_SEARCH=§7Typ: §e{0} -SCHEM_SELECTOR_FILTER_MAT=§7Nach Item filtern... -SCHEM_SELECTOR_FILTER_MAT_SEARCH=§7Item: §e{0} -SCHEM_SELECTOR_CANCEL=§eAbbrechen -SCHEM_SELECTOR_GO=§eSuchen... +SCHEM_SELECTOR_FILTER_OWNER=§7Nach Besitzer suchen... +SCHEM_SELECTOR_FILTER_OWNER_SEARCH=§7Besitzer: §e{0} +SCHEM_SELECTOR_FILTER_SEL_TYPE=Typ wählen... +SCHEM_SELECTOR_FILTER_TYPE=§7Nach Typ filtern... +SCHEM_SELECTOR_FILTER_TYPE_SEARCH=§7Typ: §e{0} +SCHEM_SELECTOR_FILTER_MAT=§7Nach Item filtern... +SCHEM_SELECTOR_FILTER_MAT_SEARCH=§7Item: §e{0} +SCHEM_SELECTOR_CANCEL=§eAbbrechen +SCHEM_SELECTOR_GO=§eSuchen... SCHEM_SELECTOR_SCHEMATIC=Schematic SCHEM_SELECTOR_DIRECTORY=Ordner SCHEM_SELECTOR_SCHEMATIC_NODE=Schematic/Ordner -MATERIAL_SELECTOR_TITLE=Material auswählen \ No newline at end of file +MATERIAL_SELECTOR_TITLE=Material auswählen + +BAN_TEAM={0} §e{1} §7wurde von §e{2} {3} §e§lgebannt§8. §7Grund§8: §f{4} +BAN_PERMA=§7Du bist §epermanent §e§lgebannt§8. §7Grund§8: §e{0} +BAN_UNTIL=§7Du bist §ebis zum {0} §e§lgebannt§8. §7Grund§8: §e{1} +UNBAN_ERROR=§cDer Spieler ist nicht gebannt. +UNBAN=§7Du hast §e{0} §e§lentbannt. + +MUTE_TEAM={0} §e{1} §7wurde von §e{2} {3} §e§lgemuted§8. §7Grund§8: §f{4} +MUTE_PERMA=§7Du bist §epermanent §e§lgemuted§8. §7Grund§8: §e{0} +MUTE_UNTIL=§7Du bist §ebis zum {0} §e§lgemuted§8. §7Grund§8: §e{1} +UNMUTE_ERROR=§cDer Spieler ist nicht gemuted. +UNMUTE=§7Du hast §e{0} §e§lentmuted. + +NOSCHEMRECEIVING_TEAM={0} §e{1} §7wurde von §e{2} {3} §7vom §e§lSchematicerhalten ausgeschlossen§8. §7Grund§8: §f{4} +NOSCHEMRECEIVING_PERMA=§7Du bist §epermanent §7vom Erhalten von §e§lSchematics ausgeschlossen§8. §7Grund§8: §e{0} +NOSCHEMRECEIVING_UNTIL=§7Du bist §ebis zum {0} §7vom Erhalten von §e§lSchematics ausgeschlossen§8. §7Grund§8: §e{1} +UNNOSCHEMRECEIVING_ERROR=§cDer Spieler ist nicht vom Erhalten von Schematics ausgeschlossen. +UNNOSCHEMRECEIVING=§e{0} §7darf nun wieder §e§lSchematics erhalten§8. + +NOSCHEMSHARING_TEAM={0} §e{1} §7wurde von §e{2} {3} §7vom §e§lSchematicverteilen ausgeschlossen§8. §7Grund§8: §f{4} +NOSCHEMSHARING_PERMA=§7Du bist §epermanent §7vom §e§lVerteilen von Schematics§7 ausgeschlossen§8. §7Grund§8: §e{0} +NOSCHEMSHARING_UNTIL=§7Du bist §ebis zum {0} §7vom §e§lVerteilen von Schematics§7 ausgeschlossen§8. §7Grund§8: §e{1} +UNNOSCHEMSHARING_ERROR=§cDer Spieler ist nicht vom Verteilen von Schematics ausgeschlossen. +UNNOSCHEMSHARING=§e{0} §7darf nun wieder §e§lSchematics verteilen§8. + +NOSCHEMSUBMITTING_TEAM={0} §e{1} §7wurde von §e{2} {3} §7vom §e§lSchematiceinsenden ausgeschlossen§8. §7Grund§8: §f{4} +NOSCHEMSUBMITTING_PERMA=§7Du bist §epermanent §7vom §e§lEinsenden von Schematics§7 ausgeschlossen§8. §7Grund§8: §e{0} +NOSCHEMSUBMITTING_UNTIL=§7Du bist §ebis zum {0} §7vom §e§lEinsenden von Schematics§7 ausgeschlossen§8. §7Grund§8: §e{1} +UNNOSCHEMSUBMITTING_ERROR=§cDer Spieler ist nicht vom Einsenden von Schematics ausgeschlossen. +UNNOSCHEMSUBMITTING=§e{0} §7darf nun wieder §e§lSchematis einsenden§8. + +NODEVSERVER_TEAM={0} §e{1} §7hat §e{2} §7mit Grund §f{4}§7 zu generft und hat daher §e§lDevserververbot §7erhalten§8, §e{3} +NODEVSERVER_PERMA=§7Du bist §epermanent §7vom §e§lDevserver §7ausgeschlossen§8. §7Grund§8: §e{0} +NODEVSERVER_UNTIL=§7Du bist §ebis zum {0} §7vom §e§lDevserver §7ausgeschlossen§8. §7Grund§8: §e{1} +UNNODEVSERVER_ERROR=§cDer Spieler ist nicht vom Devserver ausgeschlossen. +UNNODEVSERVER=§e{0} §7darf nun wieder dem §e§lDevserver beitreten§8. \ No newline at end of file diff --git a/SpigotCore_Main/src/com/comphenix/tinyprotocol/Reflection.java b/SpigotCore_Main/src/com/comphenix/tinyprotocol/Reflection.java index 1e18232..1742df4 100644 --- a/SpigotCore_Main/src/com/comphenix/tinyprotocol/Reflection.java +++ b/SpigotCore_Main/src/com/comphenix/tinyprotocol/Reflection.java @@ -1,5 +1,6 @@ package com.comphenix.tinyprotocol; +import de.steamwar.core.Core; import org.bukkit.Bukkit; import java.lang.reflect.Constructor; @@ -25,7 +26,7 @@ public final class Reflection { * @param arguments - the arguments to pass to the constructor. * @return The constructed object. */ - public Object invoke(Object... arguments); + Object invoke(Object... arguments); } /** @@ -39,7 +40,7 @@ public final class Reflection { * @param arguments - the arguments to pass to the method. * @return The return value, or NULL if is void. */ - public Object invoke(Object target, Object... arguments); + Object invoke(Object target, Object... arguments); } /** @@ -54,7 +55,7 @@ public final class Reflection { * @param target - the target object, or NULL for a static field. * @return The value of the field. */ - public T get(Object target); + T get(Object target); /** * Set the content of a field. @@ -62,7 +63,7 @@ public final class Reflection { * @param target - the target object, or NULL for a static field. * @param value - the new value of the field. */ - public void set(Object target, Object value); + void set(Object target, Object value); /** * Determine if the given object has this field. @@ -70,16 +71,16 @@ public final class Reflection { * @param target - the object to test. * @return TRUE if it does, FALSE otherwise. */ - public boolean hasField(Object target); + boolean hasField(Object target); } // Deduce the net.minecraft.server.v* package - private static String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName(); - private static String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server"); - private static String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", ""); + private static final String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName(); + private static final String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server"); + private static final String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", ""); // Variable replacement - private static Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}"); + private static final Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}"); private Reflection() { // Seal class @@ -148,7 +149,7 @@ public final class Reflection { try { return (T) field.get(target); } catch (IllegalAccessException e) { - throw new RuntimeException("Cannot access reflection.", e); + throw new IllegalArgumentException("Cannot access reflection.", e); } } @@ -157,7 +158,7 @@ public final class Reflection { try { field.set(target, value); } catch (IllegalAccessException e) { - throw new RuntimeException("Cannot access reflection.", e); + throw new IllegalArgumentException("Cannot access reflection.", e); } } @@ -220,17 +221,12 @@ public final class Reflection { && Arrays.equals(method.getParameterTypes(), params)) { method.setAccessible(true); - return new MethodInvoker() { - - @Override - public Object invoke(Object target, Object... arguments) { - try { - return method.invoke(target, arguments); - } catch (Exception e) { - throw new RuntimeException("Cannot invoke method " + method, e); - } + return (target, arguments) -> { + try { + return method.invoke(target, arguments); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot invoke method " + method, e); } - }; } } @@ -267,17 +263,12 @@ public final class Reflection { if (Arrays.equals(constructor.getParameterTypes(), params)) { constructor.setAccessible(true); - return new ConstructorInvoker() { - - @Override - public Object invoke(Object... arguments) { - try { - return constructor.newInstance(arguments); - } catch (Exception e) { - throw new RuntimeException("Cannot invoke constructor " + constructor, e); - } + return arguments -> { + try { + return constructor.newInstance(arguments); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot invoke constructor " + constructor, e); } - }; } } @@ -290,8 +281,7 @@ public final class Reflection { *

* This is useful when looking up fields by a NMS or OBC type. *

- * - * @see {@link #getClass()} for more information. + * * @param lookupName - the class name with variables. * @return The class. */ @@ -330,40 +320,10 @@ public final class Reflection { * @throws IllegalArgumentException If a variable or class could not be found. */ public static Class getClass(String lookupName) { - return getCanonicalClass(expandVariables(lookupName)); - } - - /** - * Retrieve a class in the net.minecraft.server.VERSION.* package. - * - * @param name - the name of the class, excluding the package. - * @throws IllegalArgumentException If the class doesn't exist. - */ - public static Class getMinecraftClass(String name) { - return getCanonicalClass(NMS_PREFIX + "." + name); - } - - /** - * Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package. - * - * @param name - the name of the class, excluding the package. - * @throws IllegalArgumentException If the class doesn't exist. - */ - public static Class getCraftBukkitClass(String name) { - return getCanonicalClass(OBC_PREFIX + "." + name); - } - - /** - * Retrieve a class by its canonical name. - * - * @param canonicalName - the canonical name. - * @return The class. - */ - private static Class getCanonicalClass(String canonicalName) { try { - return Class.forName(canonicalName); + return Class.forName(expandVariables(lookupName)); } catch (ClassNotFoundException e) { - throw new IllegalArgumentException("Cannot find " + canonicalName, e); + throw new IllegalArgumentException("Cannot find " + expandVariables(lookupName), e); } } @@ -379,14 +339,17 @@ public final class Reflection { while (matcher.find()) { String variable = matcher.group(1); - String replacement = ""; + String replacement; // Expand all detected variables - if ("nms".equalsIgnoreCase(variable)) - replacement = NMS_PREFIX; - else if ("obc".equalsIgnoreCase(variable)) + if (variable.startsWith("nms")) { + if(Core.getVersion() >= 17) + replacement = "net.minecraft" + variable.substring(3); + else + replacement = NMS_PREFIX; + } else if ("obc".equals(variable)) replacement = OBC_PREFIX; - else if ("version".equalsIgnoreCase(variable)) + else if ("version".equals(variable)) replacement = VERSION; else throw new IllegalArgumentException("Unknown variable: " + variable); @@ -401,6 +364,7 @@ public final class Reflection { return output.toString(); } + @SuppressWarnings("deprecation") public static Object newInstance(Class clazz) { try { return clazz.newInstance(); diff --git a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java index 3d2e41a..658262f 100644 --- a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java +++ b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java @@ -5,6 +5,7 @@ import com.comphenix.tinyprotocol.Reflection.MethodInvoker; import com.google.common.collect.Lists; import com.google.common.collect.MapMaker; import com.mojang.authlib.GameProfile; +import de.steamwar.core.Core; import io.netty.channel.*; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -28,72 +29,88 @@ import java.util.logging.Level; * * @author Kristian */ -public abstract class TinyProtocol { +public class TinyProtocol { private static final AtomicInteger ID = new AtomicInteger(0); // Used in order to lookup a channel private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle"); - private static final FieldAccessor getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class); - private static final FieldAccessor getManager = Reflection.getField("{nms}.PlayerConnection", "networkManager", Object.class); - private static final FieldAccessor getChannel = Reflection.getField("{nms}.NetworkManager", Channel.class, 0); + private static final Class playerConnection = Reflection.getUntypedClass("{nms.server.network}.PlayerConnection"); + private static final FieldAccessor getConnection = Reflection.getField("{nms.server.level}.EntityPlayer", playerConnection, 0); + private static final Class networkManager = Reflection.getUntypedClass("{nms.network}.NetworkManager"); + private static final FieldAccessor getManager = Reflection.getField(playerConnection, networkManager, 0); + private static final FieldAccessor getChannel = Reflection.getField(networkManager, Channel.class, 0); // Looking up ServerConnection - private static final Class minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer"); - private static final Class serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection"); + private static final Class minecraftServerClass = Reflection.getUntypedClass("{nms.server}.MinecraftServer"); + private static final Class serverConnectionClass = Reflection.getUntypedClass("{nms.server.network}.ServerConnection"); private static final FieldAccessor getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0); private static final FieldAccessor getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0); - private static final MethodInvoker getNetworkMarkers; - static { - MethodInvoker networkMarkers; - try { - networkMarkers = Reflection.getTypedMethod(serverConnectionClass, null, List.class, serverConnectionClass); - } catch (IllegalStateException e) { // Paper, wtf why. - networkMarkers = Reflection.getTypedMethod(serverConnectionClass, null, Queue.class, serverConnectionClass); - } - getNetworkMarkers = networkMarkers; - } + private static final FieldAccessor getNetworkMarkers = Reflection.getField(serverConnectionClass, Collection.class, 1); // Packets we have to intercept - private static final Class PACKET_LOGIN_IN_START = Reflection.getMinecraftClass("PacketLoginInStart"); + private static final Class PACKET_LOGIN_IN_START = Reflection.getClass("{nms.network.protocol.login}.PacketLoginInStart"); private static final FieldAccessor getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0); // Speedup channel lookup - private Map channelLookup = new MapMaker().weakValues().makeMap(); - private Listener listener; + private final Map channelLookup = new MapMaker().weakValues().makeMap(); + private final Listener listener; // Channels that have already been removed - private Set uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); + private final Set uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); // List of network markers private Collection networkManagers; // Injected channel handlers - private List serverChannels = Lists.newArrayList(); + private final List serverChannels = Lists.newArrayList(); private ChannelInboundHandlerAdapter serverChannelHandler; private ChannelInitializer beginInitProtocol; private ChannelInitializer endInitProtocol; // Current handler name - private String handlerName; + private final String handlerName; + private volatile boolean closed; + private final Plugin plugin; - protected volatile boolean closed; - protected Plugin plugin; + private PacketFilter inFilter = (player, channel, packet) -> packet; + private PacketFilter outFilter = (player, channel, packet) -> packet; + + public static final TinyProtocol instance = new TinyProtocol(Core.getInstance()); + + public static void init() { + //enforce init + } - /** - * Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients. - *

- * You can construct multiple instances per plugin. - * - * @param plugin - the plugin. - */ public TinyProtocol(final Plugin plugin) { this.plugin = plugin; // Compute handler name - this.handlerName = getHandlerName(); + this.handlerName = "tiny-" + plugin.getName() + "-" + ID.incrementAndGet(); // Prepare existing players - registerBukkitEvents(); + listener = new Listener() { + + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerLogin(PlayerLoginEvent e) { + if (closed) + return; + + Channel channel = getChannel(e.getPlayer()); + + // Don't inject players that have been explicitly uninjected + if (!uninjectedChannels.contains(channel)) { + injectPlayer(e.getPlayer()); + } + } + + @EventHandler + public void onPluginDisable(PluginDisableEvent e) { + if (e.getPlugin().equals(plugin)) { + close(); + } + } + }; + plugin.getServer().getPluginManager().registerEvents(listener, plugin); try { registerChannelHandler(); @@ -158,37 +175,6 @@ public abstract class TinyProtocol { }; } - /** - * Register bukkit events. - */ - private void registerBukkitEvents() { - listener = new Listener() { - - @EventHandler(priority = EventPriority.LOWEST) - public final void onPlayerLogin(PlayerLoginEvent e) { - if (closed) - return; - - Channel channel = getChannel(e.getPlayer()); - - // Don't inject players that have been explicitly uninjected - if (!uninjectedChannels.contains(channel)) { - injectPlayer(e.getPlayer()); - } - } - - @EventHandler - public final void onPluginDisable(PluginDisableEvent e) { - if (e.getPlugin().equals(plugin)) { - close(); - } - } - - }; - - plugin.getServer().getPluginManager().registerEvents(listener, plugin); - } - @SuppressWarnings("unchecked") private void registerChannelHandler() { Object mcServer = getMinecraftServer.get(Bukkit.getServer()); @@ -196,7 +182,7 @@ public abstract class TinyProtocol { boolean looking = true; // We need to synchronize against this list - networkManagers = (Collection) getNetworkMarkers.invoke(null, serverConnection); + networkManagers = getNetworkMarkers.get(serverConnection); createServerChannelHandler(); // Find the correct list, or implicitly throw an exception @@ -204,7 +190,7 @@ public abstract class TinyProtocol { List list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection); for (Object item : list) { - if (!ChannelFuture.class.isInstance(item)) + if (!(item instanceof ChannelFuture)) break; // Channel future that contains the server connection @@ -225,17 +211,12 @@ public abstract class TinyProtocol { final ChannelPipeline pipeline = serverChannel.pipeline(); // Remove channel handler - serverChannel.eventLoop().execute(new Runnable() { - - @Override - public void run() { - try { - pipeline.remove(serverChannelHandler); - } catch (NoSuchElementException e) { - // That's fine - } + serverChannel.eventLoop().execute(() -> { + try { + pipeline.remove(serverChannelHandler); + } catch (NoSuchElementException e) { + // That's fine } - }); } } @@ -246,120 +227,46 @@ public abstract class TinyProtocol { } } - /** - * Invoked when the server is starting to send a packet to a player. - *

- * Note that this is not executed on the main thread. - * - * @param receiver - the receiving player, NULL for early login/status packets. - * @param channel - the channel that received the packet. Never NULL. - * @param packet - the packet being sent. - * @return The packet to send instead, or NULL to cancel the transmission. - */ public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) { - return packet; + return outFilter.onPacket(receiver, channel, packet); } - /** - * Invoked when the server has received a packet from a given player. - *

- * Use {@link Channel#remoteAddress()} to get the remote address of the client. - * - * @param sender - the player that sent the packet, NULL for early login/status packets. - * @param channel - channel that received the packet. Never NULL. - * @param packet - the packet being received. - * @return The packet to recieve instead, or NULL to cancel. - */ public Object onPacketInAsync(Player sender, Channel channel, Object packet) { - return packet; + return inFilter.onPacket(sender, channel, packet); + } + + public void setInFilter(PacketFilter filter) { + inFilter = filter; + } + + public void setOutFilter(PacketFilter filter) { + outFilter = filter; } - /** - * Send a packet to a particular player. - *

- * Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet. - * - * @param player - the destination player. - * @param packet - the packet to send. - */ public void sendPacket(Player player, Object packet) { sendPacket(getChannel(player), packet); } - /** - * Send a packet to a particular client. - *

- * Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet. - * - * @param channel - client identified by a channel. - * @param packet - the packet to send. - */ public void sendPacket(Channel channel, Object packet) { channel.pipeline().writeAndFlush(packet); } - /** - * Pretend that a given packet has been received from a player. - *

- * Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet. - * - * @param player - the player that sent the packet. - * @param packet - the packet that will be received by the server. - */ public void receivePacket(Player player, Object packet) { receivePacket(getChannel(player), packet); } - /** - * Pretend that a given packet has been received from a given client. - *

- * Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet. - * - * @param channel - client identified by a channel. - * @param packet - the packet that will be received by the server. - */ public void receivePacket(Channel channel, Object packet) { channel.pipeline().context("encoder").fireChannelRead(packet); } - /** - * Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID. - *

- * Note that this method will only be invoked once. It is no longer necessary to override this to support multiple instances. - * - * @return A unique channel handler name. - */ - protected String getHandlerName() { - return "tiny-" + plugin.getName() + "-" + ID.incrementAndGet(); - } - - /** - * Add a custom channel handler to the given player's channel pipeline, allowing us to intercept sent and received packets. - *

- * This will automatically be called when a player has logged in. - * - * @param player - the player to inject. - */ public void injectPlayer(Player player) { injectChannelInternal(getChannel(player)).player = player; } - /** - * Add a custom channel handler to the given channel. - * - * @param channel - the channel to inject. - * @return The intercepted channel, or NULL if it has already been injected. - */ public void injectChannel(Channel channel) { injectChannelInternal(channel); } - /** - * Add a custom channel handler to the given channel. - * - * @param channel - the channel to inject. - * @return The packet interceptor. - */ private PacketInterceptor injectChannelInternal(Channel channel) { try { PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName); @@ -378,12 +285,6 @@ public abstract class TinyProtocol { } } - /** - * Retrieve the Netty channel associated with a player. This is cached. - * - * @param player - the player. - * @return The Netty channel. - */ public Channel getChannel(Player player) { Channel channel = channelLookup.get(player.getName()); @@ -398,22 +299,10 @@ public abstract class TinyProtocol { return channel; } - /** - * Uninject a specific player. - * - * @param player - the injected player. - */ public void uninjectPlayer(Player player) { uninjectChannel(getChannel(player)); } - /** - * Uninject a specific channel. - *

- * This will also disable the automatic channel injection that occurs when a player has properly logged in. - * - * @param channel - the injected channel. - */ public void uninjectChannel(final Channel channel) { // No need to guard against this if we're closing if (!closed) { @@ -421,39 +310,23 @@ public abstract class TinyProtocol { } // See ChannelInjector in ProtocolLib, line 590 - channel.eventLoop().execute(new Runnable() { - - @Override - public void run() { + channel.eventLoop().execute(() -> { + try { channel.pipeline().remove(handlerName); + } catch (NoSuchElementException e) { + // ignore } - }); } - /** - * Determine if the given player has been injected by TinyProtocol. - * - * @param player - the player. - * @return TRUE if it is, FALSE otherwise. - */ public boolean hasInjected(Player player) { return hasInjected(getChannel(player)); } - /** - * Determine if the given channel has been injected by TinyProtocol. - * - * @param channel - the channel. - * @return TRUE if it is, FALSE otherwise. - */ public boolean hasInjected(Channel channel) { return channel.pipeline().get(handlerName) != null; } - /** - * Cease listening for packets. This is called automatically when your plugin is disabled. - */ public final void close() { if (!closed) { closed = true; @@ -469,11 +342,10 @@ public abstract class TinyProtocol { } } - /** - * Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets. - * - * @author Kristian - */ + public interface PacketFilter { + Object onPacket(Player player, Channel channel, Object packet); + } + private final class PacketInterceptor extends ChannelDuplexHandler { // Updated by the login event public volatile Player player; diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandFrameworkException.java b/SpigotCore_Main/src/de/steamwar/command/CommandFrameworkException.java new file mode 100644 index 0000000..74db6d3 --- /dev/null +++ b/SpigotCore_Main/src/de/steamwar/command/CommandFrameworkException.java @@ -0,0 +1,76 @@ +/* + * 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 java.io.PrintStream; +import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; + +public class CommandFrameworkException extends RuntimeException { + + private InvocationTargetException invocationTargetException; + private String alias; + private String[] args; + + private String message; + + CommandFrameworkException(InvocationTargetException invocationTargetException, String alias, String[] args) { + super(invocationTargetException); + this.invocationTargetException = invocationTargetException; + this.alias = alias; + this.args = args; + } + + public synchronized String getBuildStackTrace() { + if (message != null) { + return message; + } + StackTraceElement[] stackTraceElements = invocationTargetException.getCause().getStackTrace(); + StringBuilder st = new StringBuilder(); + st.append(invocationTargetException.getCause().getClass().getTypeName()); + if (invocationTargetException.getCause().getMessage() != null) { + st.append(": ").append(invocationTargetException.getCause().getMessage()); + } + st.append("\n"); + if (alias != null && !alias.isEmpty()) { + st.append("Performed command: ").append(alias).append(" ").append(String.join(" ", args)).append("\n"); + } + for (int i = 0; i < stackTraceElements.length - invocationTargetException.getStackTrace().length; i++) { + st.append("\tat ").append(stackTraceElements[i].toString()).append("\n"); + } + message = st.toString(); + return message; + } + + @Override + public void printStackTrace() { + printStackTrace(System.err); + } + + @Override + public void printStackTrace(PrintStream s) { + s.print(getBuildStackTrace()); + } + + @Override + public void printStackTrace(PrintWriter s) { + s.print(getBuildStackTrace()); + } +} diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandNoHelpException.java b/SpigotCore_Main/src/de/steamwar/command/CommandNoHelpException.java index 41b487b..e3d476a 100644 --- a/SpigotCore_Main/src/de/steamwar/command/CommandNoHelpException.java +++ b/SpigotCore_Main/src/de/steamwar/command/CommandNoHelpException.java @@ -21,6 +21,5 @@ package de.steamwar.command; class CommandNoHelpException extends RuntimeException { - CommandNoHelpException() { - } + CommandNoHelpException() {} } diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java b/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java index 21e68bb..3d81ea6 100644 --- a/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java +++ b/SpigotCore_Main/src/de/steamwar/command/CommandParseException.java @@ -19,7 +19,7 @@ package de.steamwar.command; -public class CommandParseException extends Exception { +public class CommandParseException extends RuntimeException { public CommandParseException() { } diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandPart.java b/SpigotCore_Main/src/de/steamwar/command/CommandPart.java new file mode 100644 index 0000000..d0e7fb7 --- /dev/null +++ b/SpigotCore_Main/src/de/steamwar/command/CommandPart.java @@ -0,0 +1,217 @@ +/* + * 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 lombok.AllArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.bukkit.command.CommandSender; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; + +@ToString +public class CommandPart { + private static final String[] EMPTY_ARRAY = new String[0]; + + @AllArgsConstructor + private static class CheckArgumentResult { + private final boolean success; + private final Object value; + } + + private TypeMapper typeMapper; + private GuardChecker guard; + private Class varArgType; + private String optional; + private GuardCheckType guardCheckType; + + private CommandPart next = null; + + @Setter + private boolean ignoreAsArgument = false; + + public CommandPart(TypeMapper typeMapper, GuardChecker guard, Class varArgType, String optional, GuardCheckType guardCheckType) { + this.typeMapper = typeMapper; + this.guard = guard; + this.varArgType = varArgType; + this.optional = optional; + this.guardCheckType = guardCheckType; + + validatePart(); + } + + public void setNext(CommandPart next) { + if (varArgType != null) { + throw new IllegalArgumentException("There can't be a next part if this is a vararg part!"); + } + this.next = next; + } + + private void validatePart() { + if (guardCheckType == GuardCheckType.TAB_COMPLETE) { + throw new IllegalArgumentException("Tab complete is not allowed as a guard check type!"); + } + if (optional != null && varArgType != null) { + throw new IllegalArgumentException("A vararg part can't have an optional part!"); + } + + if (optional != null) { + try { + typeMapper.map(null, EMPTY_ARRAY, optional); + } catch (Exception e) { + throw new IllegalArgumentException("The optional part is not valid!"); + } + } + } + + public void generateArgumentArray(List current, CommandSender commandSender, String[] args, int startIndex) { + if (varArgType != null) { + Object array = Array.newInstance(varArgType, args.length - startIndex); + for (int i = startIndex; i < args.length; i++) { + CheckArgumentResult validArgument = checkArgument(null, commandSender, args, i); + if (!validArgument.success) { + throw new CommandParseException(); + } + Array.set(array, i - startIndex, validArgument.value); + } + current.add(array); + return; + } + + CheckArgumentResult validArgument = checkArgument(null, commandSender, args, startIndex); + if (!validArgument.success && optional == null) { + throw new CommandParseException(); + } + if (!validArgument.success) { + if (!ignoreAsArgument) { + current.add(typeMapper.map(commandSender, EMPTY_ARRAY, optional)); + } + if (next != null) { + next.generateArgumentArray(current, commandSender, args, startIndex); + } + return; + } + if (!ignoreAsArgument) { + current.add(validArgument.value); + } + if (next != null) { + next.generateArgumentArray(current, commandSender, args, startIndex + 1); + } + } + + public boolean guardCheck(CommandSender commandSender, String[] args, int startIndex) { + if (varArgType != null) { + for (int i = startIndex; i < args.length; i++) { + GuardResult guardResult = checkGuard(guardCheckType, commandSender, args, i); + if (guardResult == GuardResult.DENIED) { + throw new CommandNoHelpException(); + } + if (guardResult == GuardResult.DENIED_WITH_HELP) { + return false; + } + } + return true; + } + + GuardResult guardResult = checkGuard(guardCheckType, commandSender, args, startIndex); + if (guardResult == GuardResult.DENIED) { + if (optional != null && next != null) { + return next.guardCheck(commandSender, args, startIndex); + } + throw new CommandNoHelpException(); + } + if (guardResult == GuardResult.DENIED_WITH_HELP) { + if (optional != null && next != null) { + return next.guardCheck(commandSender, args, startIndex); + } + return false; + } + if (next != null) { + return next.guardCheck(commandSender, args, startIndex + 1); + } + return true; + } + + public void generateTabComplete(List current, CommandSender commandSender, String[] args, int startIndex) { + if (varArgType != null) { + for (int i = startIndex; i < args.length - 1; i++) { + CheckArgumentResult validArgument = checkArgument(null, commandSender, args, i); + if (!validArgument.success) { + return; + } + } + List strings = typeMapper.tabCompletes(commandSender, Arrays.copyOf(args, args.length - 1), args[args.length - 1]); + if (strings != null) { + current.addAll(strings); + } + return; + } + + if (args.length - 1 > startIndex) { + CheckArgumentResult checkArgumentResult = checkArgument(GuardCheckType.TAB_COMPLETE, commandSender, args, startIndex); + if (checkArgumentResult.success && next != null) { + next.generateTabComplete(current, commandSender, args, startIndex + 1); + return; + } + if (optional != null && next != null) { + next.generateTabComplete(current, commandSender, args, startIndex); + } + return; + } + + List strings = typeMapper.tabCompletes(commandSender, Arrays.copyOf(args, startIndex), args[startIndex]); + if (strings != null) { + current.addAll(strings); + } + if (optional != null && next != null) { + next.generateTabComplete(current, commandSender, args, startIndex); + } + } + + private CheckArgumentResult checkArgument(GuardCheckType guardCheckType, CommandSender commandSender, String[] args, int index) { + try { + Object value = typeMapper.map(commandSender, Arrays.copyOf(args, index), args[index]); + if (value == null) { + return new CheckArgumentResult(false, null); + } + GuardResult guardResult = checkGuard(guardCheckType, commandSender, args, index); + switch (guardResult) { + case ALLOWED: + return new CheckArgumentResult(true, value); + case DENIED: + throw new CommandNoHelpException(); + case DENIED_WITH_HELP: + default: + return new CheckArgumentResult(false, null); + } + } catch (Exception e) { + return new CheckArgumentResult(false, null); + } + } + + private GuardResult checkGuard(GuardCheckType guardCheckType, CommandSender commandSender, String[] args, int index) { + if (guard != null && guardCheckType != null) { + return guard.guard(commandSender, guardCheckType, Arrays.copyOf(args, index), args[index]); + } + return GuardResult.ALLOWED; + } +} diff --git a/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java b/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java new file mode 100644 index 0000000..143ea2a --- /dev/null +++ b/SpigotCore_Main/src/de/steamwar/command/CommandRegistering.java @@ -0,0 +1,65 @@ +/* + * 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 lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.command.SimpleCommandMap; + +import java.lang.reflect.Field; +import java.util.Map; + +@UtilityClass +class CommandRegistering { + + private static final CommandMap commandMap; + private static final Map 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) knownCommandsField.get(commandMap); + } catch (NoSuchFieldException | IllegalAccessException exception) { + Bukkit.shutdown(); + throw new SecurityException("Oh shit. Commands cannot be registered.", exception); + } + } + + static void unregister(Command command) { + knownCommandMap.remove(command.getName()); + command.getAliases().forEach(knownCommandMap::remove); + command.unregister(commandMap); + } + + static void register(Command command) { + commandMap.register("steamwar", command); + } +} diff --git a/SpigotCore_Main/src/de/steamwar/command/SWCommand.java b/SpigotCore_Main/src/de/steamwar/command/SWCommand.java index 258ab5c..6d58c5b 100644 --- a/SpigotCore_Main/src/de/steamwar/command/SWCommand.java +++ b/SpigotCore_Main/src/de/steamwar/command/SWCommand.java @@ -61,13 +61,7 @@ public abstract class SWCommand { if (!initialized) { createMapping(); } - try { - if (!commandList.stream().anyMatch(s -> s.invoke(sender, args))) { - commandHelpList.stream().anyMatch(s -> s.invoke(sender, args)); - } - } catch (CommandNoHelpException e) { - // Ignored - } + SWCommand.this.execute(sender, alias, args); return false; } @@ -76,20 +70,57 @@ public abstract class SWCommand { if (!initialized) { createMapping(); } - 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()); + return SWCommand.this.tabComplete(sender, alias, args); } }; unregister(); register(); } + // This is used for the tests! + SWCommand(boolean noRegister, String command, String... aliases) { + this.command = new Command(command, "", "/" + command, Arrays.asList(aliases)) { + @Override + public boolean execute(CommandSender sender, String alias, String[] args) { + SWCommand.this.execute(sender, alias, args); + return false; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + return SWCommand.this.tabComplete(sender, alias, args); + } + }; + createMapping(); + } + + void execute(CommandSender sender, String alias, String[] args) { + try { + if (!commandList.stream().anyMatch(s -> s.invoke(sender, alias, args))) { + commandHelpList.stream().anyMatch(s -> s.invoke(sender, alias, args)); + } + } catch (CommandNoHelpException e) { + // Ignored + } catch (CommandFrameworkException e) { + if (Bukkit.getServer() != null) { + Bukkit.getLogger().log(Level.SEVERE, "", e); + } + throw e; + } + } + + List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + String string = args[args.length - 1].toLowerCase(); + return commandList.stream() + .filter(s -> !s.noTabComplete) + .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()); + } + private synchronized void createMapping() { List methods = methods(); for (Method method : methods) { @@ -118,7 +149,7 @@ public abstract class SWCommand { 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<>(), localGuardChecker, true, null)); + commandHelpList.add(new SubCommand(this, method, anno.value(), new HashMap<>(), localGuardChecker, true, null, anno.noTabComplete())); }); } for (Method method : methods) { @@ -140,7 +171,7 @@ public abstract class SWCommand { return; } } - commandList.add(new SubCommand(this, method, anno.value(), localTypeMapper, localGuardChecker, false, anno.description())); + commandList.add(new SubCommand(this, method, anno.value(), localTypeMapper, localGuardChecker, false, anno.description(), anno.noTabComplete())); }); this.commandList.sort((o1, o2) -> { @@ -148,8 +179,7 @@ public abstract class SWCommand { 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); + return Integer.compare(o1.comparableValue, o2.comparableValue); } }); commandHelpList.sort((o1, o2) -> { @@ -218,13 +248,11 @@ public abstract class SWCommand { } public void unregister() { - SWCommandUtils.knownCommandMap.remove(command.getName()); - command.getAliases().forEach(SWCommandUtils.knownCommandMap::remove); - command.unregister(SWCommandUtils.commandMap); + CommandRegistering.unregister(command); } public void register() { - SWCommandUtils.commandMap.register("steamwar", this.command); + CommandRegistering.register(command); } @Register(help = true) @@ -287,6 +315,8 @@ public abstract class SWCommand { String[] description() default {}; + boolean noTabComplete() default false; + @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @interface Registeres { @@ -325,4 +355,29 @@ public abstract class SWCommand { boolean local() default false; } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + protected @interface StaticValue { + String[] value(); + + /** + * This is the short form for 'allowImplicitSwitchExpressions' + * and can be set to true if you want to allow int as well as boolean as annotated parameter types. + * The value array needs to be at least 2 long for this flag to be considered. + * While using an int, the value will represent the index into the value array. + * While using a boolean, the value array must only be 2 long and the value will be {@code false} + * for the first index and {@code true} for the second index. + */ + boolean allowISE() default false; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.PARAMETER}) + protected @interface OptionalValue { + /** + * Will pe parsed against the TypeMapper specified by the parameter or annotation. + */ + String value(); + } } diff --git a/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java b/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java index 1d7110f..3e18811 100644 --- a/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java +++ b/SpigotCore_Main/src/de/steamwar/command/SWCommandUtils.java @@ -19,20 +19,17 @@ package de.steamwar.command; +import de.steamwar.sql.BauweltMember; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SteamwarUser; 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.lang.reflect.Parameter; import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; @@ -47,15 +44,6 @@ public class SWCommandUtils { static final Map> MAPPER_FUNCTIONS = new HashMap<>(); static final Map GUARD_FUNCTIONS = new HashMap<>(); - static final TypeMapper ERROR_FUNCTION = createMapper(s -> { - throw new SecurityException(); - }, s -> Collections.emptyList()); - - static final BiFunction>, String, Enum> ENUM_MAPPER = (enumClass, s) -> { - Enum[] enums = enumClass.getEnumConstants(); - return Arrays.stream(enums).filter(e -> e.name().equalsIgnoreCase(s)).findFirst().orElse(null); - }; - static { addMapper(boolean.class, Boolean.class, createMapper(Boolean::parseBoolean, s -> Arrays.asList("true", "false"))); addMapper(float.class, Float.class, createMapper(numberMapper(Float::parseFloat), numberCompleter(Float::parseFloat))); @@ -75,13 +63,39 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(SteamwarUser.class.getTypeName(), createMapper(SteamwarUser::get, s -> Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList()))); MAPPER_FUNCTIONS.put(SchematicNode.class.getTypeName(), new TypeMapper() { @Override - public List tabCompletes(CommandSender commandSender, String[] strings, String s) { - return SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); + public SchematicNode map(CommandSender commandSender, String[] previousArguments, String s) { + return SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); } @Override - public SchematicNode map(CommandSender commandSender, String[] previousArguments, String s) { - return SchematicNode.getNodeFromPath(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); + public List tabCompletes(CommandSender commandSender, String[] strings, String s) { + return SchematicNode.getNodeTabcomplete(SteamwarUser.get(((Player) commandSender).getUniqueId()), s); + } + }); + MAPPER_FUNCTIONS.put(BauweltMember.class.getTypeName(), new TypeMapper() { + @Override + public BauweltMember map(CommandSender commandSender, String[] previousArguments, String s) { + if (!(commandSender instanceof Player)) { + return null; + } + Player player = (Player) commandSender; + return BauweltMember.getMembers(player.getUniqueId()) + .stream() + .filter(member -> SteamwarUser.get(member.getMemberID()).getUserName().equalsIgnoreCase(s)) + .findAny() + .orElse(null); + } + + @Override + public List tabCompletes(CommandSender commandSender, String[] previousArguments, String s) { + if (!(commandSender instanceof Player)) { + return new ArrayList<>(); + } + Player player = (Player) commandSender; + return BauweltMember.getMembers(player.getUniqueId()) + .stream() + .map(m -> SteamwarUser.get(m.getMemberID()).getUserName()) + .collect(Collectors.toList()); } }); } @@ -91,60 +105,139 @@ public class SWCommandUtils { MAPPER_FUNCTIONS.put(alternativeClazz.getTypeName(), mapper); } - static final CommandMap commandMap; - static final Map 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) knownCommandsField.get(commandMap); - } catch (NoSuchFieldException | IllegalAccessException exception) { - Bukkit.shutdown(); - throw new SecurityException("Oh shit. Commands cannot be registered.", exception); - } - } - - static Object[] generateArgumentArray(CommandSender commandSender, TypeMapper[] parameters, GuardChecker[] guards, 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++; - } - - int length = 0; - if (varArgType != null) { - length = args.length - parameters.length - subCommand.length + 1; - arguments[arguments.length - 1] = Array.newInstance(varArgType, length); - if (index > args.length - 1) return arguments; - } - - for (int i = 0; i < parameters.length - (varArgType != null ? 1 : 0); i++) { - arguments[i + 1] = parameters[i].map(commandSender, Arrays.copyOf(args, index), args[index]); - index++; - if (arguments[i + 1] == null) throw new CommandParseException(); - } - - if (varArgType != null) { - Object varArgument = arguments[arguments.length - 1]; - - for (int i = 0; i < length; i++) { - Object value = parameters[parameters.length - 1].map(commandSender, Arrays.copyOf(args, index), args[index]); - if (value == null) throw new CommandParseException(); - Array.set(varArgument, i, value); - index++; + static CommandPart generateCommandPart(boolean help, String[] subCommand, Parameter[] parameters, Map> localTypeMapper, Map localGuardChecker) { + CommandPart first = null; + CommandPart current = null; + for (String s : subCommand) { + CommandPart commandPart = new CommandPart(createMapper(s), null, null, null, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND); + commandPart.setIgnoreAsArgument(true); + if (current != null) { + current.setNext(commandPart); + } + current = commandPart; + if (first == null) { + first = current; } } - return arguments; + for (int i = 1; i < parameters.length; i++) { + Parameter parameter = parameters[i]; + TypeMapper typeMapper = getTypeMapper(parameter, localTypeMapper); + GuardChecker guardChecker = getGuardChecker(parameter, localGuardChecker); + Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; + SWCommand.OptionalValue optionalValue = parameter.getAnnotation(SWCommand.OptionalValue.class); + + CommandPart commandPart = new CommandPart(typeMapper, guardChecker, varArgType, optionalValue != null ? optionalValue.value() : null, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND); + if (current != null) { + current.setNext(commandPart); + } + current = commandPart; + if (first == null) { + first = current; + } + } + return first; + } + + static TypeMapper getTypeMapper(Parameter parameter, Map> localTypeMapper) { + Class clazz = parameter.getType(); + if (parameter.isVarArgs()) { + clazz = clazz.getComponentType(); + } + + SWCommand.ClassMapper classMapper = parameter.getAnnotation(SWCommand.ClassMapper.class); + SWCommand.Mapper mapper = parameter.getAnnotation(SWCommand.Mapper.class); + if (clazz.isEnum() && classMapper == null && mapper == null && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) { + return createEnumMapper((Class>) clazz); + } + + String name = clazz.getTypeName(); + if (classMapper != null) { + name = classMapper.value().getTypeName(); + } else if (mapper != null) { + name = mapper.value(); + } else { + SWCommand.StaticValue staticValue = parameter.getAnnotation(SWCommand.StaticValue.class); + if (staticValue != null) { + if (parameter.getType() == String.class) { + return createMapper(staticValue.value()); + } + if (staticValue.allowISE()) { + if (parameter.getType() == boolean.class && staticValue.value().length == 2) { + List tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value())); + return new TypeMapper() { + @Override + public Boolean map(CommandSender commandSender, String[] previousArguments, String s) { + int index = tabCompletes.indexOf(s); + if (index == -1) { + return null; + } + return index != 0; + } + + @Override + public List tabCompletes(CommandSender commandSender, String[] previousArguments, String s) { + return tabCompletes; + } + }; + } + if (parameter.getType() == int.class && staticValue.value().length >= 2) { + List tabCompletes = new ArrayList<>(Arrays.asList(staticValue.value())); + return new TypeMapper() { + @Override + public Integer map(CommandSender commandSender, String[] previousArguments, String s) { + int index = tabCompletes.indexOf(s); + if (index == -1) { + return null; + } + return index; + } + + @Override + public List tabCompletes(CommandSender commandSender, String[] previousArguments, String s) { + return tabCompletes; + } + }; + } + } + } + } + TypeMapper typeMapper = localTypeMapper.getOrDefault(name, MAPPER_FUNCTIONS.getOrDefault(name, null)); + if (typeMapper == null) { + throw new IllegalArgumentException("No mapper found for " + name); + } + return typeMapper; + } + + static GuardChecker getGuardChecker(Parameter parameter, Map localGuardChecker) { + Class clazz = parameter.getType(); + if (parameter.isVarArgs()) { + clazz = clazz.getComponentType(); + } + + SWCommand.ClassGuard classGuard = parameter.getAnnotation(SWCommand.ClassGuard.class); + if (classGuard != null) { + if (classGuard.value() != null) { + return getGuardChecker(classGuard.value().getTypeName(), localGuardChecker); + } + return getGuardChecker(clazz.getTypeName(), localGuardChecker); + } + + SWCommand.Guard guard = parameter.getAnnotation(SWCommand.Guard.class); + if (guard != null) { + if (guard.value() != null && !guard.value().isEmpty()) { + return getGuardChecker(guard.value(), localGuardChecker); + } + return getGuardChecker(clazz.getTypeName(), localGuardChecker); + } + return null; + } + + private static GuardChecker getGuardChecker(String s, Map localGuardChecker) { + GuardChecker guardChecker = localGuardChecker.getOrDefault(s, GUARD_FUNCTIONS.getOrDefault(s, null)); + if (guardChecker == null) { + throw new IllegalArgumentException("No guard found for " + s); + } + return guardChecker; } public static void addMapper(Class clazz, TypeMapper mapper) { @@ -163,6 +256,11 @@ public class SWCommandUtils { GUARD_FUNCTIONS.putIfAbsent(name, guardChecker); } + public static TypeMapper createMapper(String... values) { + List strings = Arrays.asList(values); + return createMapper((s) -> strings.contains(s) ? s : null, s -> strings); + } + public static TypeMapper createMapper(Function mapper, Function> tabCompleter) { return createMapper(mapper, (commandSender, s) -> tabCompleter.apply(s)); } @@ -181,10 +279,35 @@ public class SWCommandUtils { }; } + public static TypeMapper> createEnumMapper(Class> enumClass) { + Enum[] enums = enumClass.getEnumConstants(); + List strings = Arrays.stream(enums).map(Enum::name).map(String::toLowerCase).collect(Collectors.toList()); + return new TypeMapper>() { + @Override + public Enum map(CommandSender commandSender, String[] previousArguments, String s) { + for (Enum e : enums) { + if (e.name().equalsIgnoreCase(s)) return e; + } + return null; + } + + @Override + public List tabCompletes(CommandSender commandSender, String[] previousArguments, String s) { + return strings; + } + }; + } + private static Function numberMapper(Function mapper) { return s -> { + if (s.equalsIgnoreCase("nan")) return null; try { return mapper.apply(s); + } catch (NumberFormatException e) { + // Ignored + } + try { + return mapper.apply(s.replace(',', '.')); } catch (NumberFormatException e) { return null; } diff --git a/SpigotCore_Main/src/de/steamwar/command/SubCommand.java b/SpigotCore_Main/src/de/steamwar/command/SubCommand.java index e18b5f8..3254ade 100644 --- a/SpigotCore_Main/src/de/steamwar/command/SubCommand.java +++ b/SpigotCore_Main/src/de/steamwar/command/SubCommand.java @@ -19,18 +19,16 @@ package de.steamwar.command; -import org.bukkit.Bukkit; 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.ArrayList; +import java.util.List; +import java.util.Map; import java.util.function.Function; import java.util.function.Predicate; -import java.util.logging.Level; - -import static de.steamwar.command.SWCommandUtils.*; class SubCommand { @@ -38,187 +36,84 @@ class SubCommand { Method method; String[] description; String[] subCommand; - TypeMapper[] arguments; - GuardChecker[] guards; private Predicate commandSenderPredicate; private Function commandSenderFunction; GuardChecker guardChecker; - Class varArgType = null; - private boolean help; + boolean noTabComplete; + int comparableValue; - SubCommand(SWCommand swCommand, Method method, String[] subCommand, Map> localTypeMapper, Map localGuardChecker, boolean help, String[] description) { + private CommandPart commandPart; + + SubCommand(SWCommand swCommand, Method method, String[] subCommand, Map> localTypeMapper, Map localGuardChecker, boolean help, String[] description, boolean noTabComplete) { this.swCommand = swCommand; this.method = method; - this.help = help; this.description = description; + this.noTabComplete = noTabComplete; + this.subCommand = subCommand; Parameter[] parameters = method.getParameters(); + comparableValue = parameters[parameters.length - 1].isVarArgs() ? Integer.MAX_VALUE : -parameters.length; + + guardChecker = SWCommandUtils.getGuardChecker(parameters[0], localGuardChecker); + + commandPart = SWCommandUtils.generateCommandPart(help, subCommand, parameters, localTypeMapper, localGuardChecker); commandSenderPredicate = sender -> parameters[0].getType().isAssignableFrom(sender.getClass()); commandSenderFunction = sender -> parameters[0].getType().cast(sender); - this.subCommand = subCommand; - guardChecker = getGuardChecker(parameters[0], localGuardChecker); - - arguments = new TypeMapper[parameters.length - 1]; - guards = new GuardChecker[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 && !MAPPER_FUNCTIONS.containsKey(clazz.getTypeName()) && !localTypeMapper.containsKey(clazz.getTypeName())) { - Class> enumClass = (Class>) clazz; - List tabCompletes = new ArrayList<>(); - for (Enum enumConstant : enumClass.getEnumConstants()) { - tabCompletes.add(enumConstant.name().toLowerCase()); - } - arguments[i - 1] = SWCommandUtils.createMapper(s -> ENUM_MAPPER.apply(enumClass, s), s -> tabCompletes); - continue; - } - - String name = clazz.getTypeName(); - if (mapper != null) { - name = mapper.value(); - } - arguments[i - 1] = localTypeMapper.containsKey(name) - ? localTypeMapper.get(name) - : MAPPER_FUNCTIONS.getOrDefault(name, ERROR_FUNCTION); - guards[i - 1] = getGuardChecker(parameter, localGuardChecker); - } } - private GuardChecker getGuardChecker(Parameter parameter, Map localGuardChecker) { - SWCommand.Guard guard = parameter.getAnnotation(SWCommand.Guard.class); - if (guard != null) { - if (guard.value() == null || guard.value().isEmpty()) { - String s = parameter.getType().getTypeName(); - if (parameter.isVarArgs()) { - s = parameter.getType().getComponentType().getTypeName(); - } - return localGuardChecker.getOrDefault(s, GUARD_FUNCTIONS.getOrDefault(s, null)); - } - GuardChecker current = localGuardChecker.getOrDefault(guard.value(), GUARD_FUNCTIONS.getOrDefault(guard.value(), null)); - if (current == null) { - Bukkit.getLogger().log(Level.WARNING, () -> "The guard checker with name '" + guard.value() + "' is neither a local guard checker nor a global one"); - } - return current; - } - return null; - } - - 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; - } + boolean invoke(CommandSender commandSender, String alias, String[] args) { try { if (!commandSenderPredicate.test(commandSender)) { return false; } - Object[] objects = SWCommandUtils.generateArgumentArray(commandSender, arguments, guards, args, varArgType, subCommand); - objects[0] = commandSenderFunction.apply(commandSender); - for (int i = subCommand.length; i < args.length; i++) { - GuardChecker current; - if (i == subCommand.length) { - current = guardChecker; - } else { - if (i >= objects.length + subCommand.length) { - current = guards[guards.length - 1]; - } else { - current = guards[i - 1 - subCommand.length]; - } + + if (commandPart == null) { + if (args.length != 0) { + return false; } - if (current != null) { - GuardResult guardResult; - if (i == 0) { - guardResult = current.guard(commandSender, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND, new String[0], null); - } else { - guardResult = current.guard(commandSender, help ? GuardCheckType.HELP_COMMAND : GuardCheckType.COMMAND, Arrays.copyOf(args, i - 1), args[i - 1]); - } - if (guardResult != GuardResult.ALLOWED) { - if (guardResult == GuardResult.DENIED) { + method.setAccessible(true); + method.invoke(swCommand, commandSenderFunction.apply(commandSender)); + } else { + List objects = new ArrayList<>(); + commandPart.generateArgumentArray(objects, commandSender, args, 0); + if (guardChecker != null) { + GuardResult guardResult = guardChecker.guard(commandSender, GuardCheckType.COMMAND, new String[0], null); + switch (guardResult) { + case ALLOWED: + break; + case DENIED: throw new CommandNoHelpException(); - } - return false; + case DENIED_WITH_HELP: + default: + return true; } } + commandPart.guardCheck(commandSender, args, 0); + objects.add(0, commandSenderFunction.apply(commandSender)); + method.setAccessible(true); + method.invoke(swCommand, objects.toArray()); } - method.setAccessible(true); - method.invoke(swCommand, objects); } catch (CommandNoHelpException e) { throw e; - } catch (IllegalAccessException | RuntimeException | InvocationTargetException e) { - throw new SecurityException(e.getMessage(), e); } catch (CommandParseException e) { return false; + } catch (InvocationTargetException e) { + throw new CommandFrameworkException(e, alias, args); + } catch (IllegalAccessException | RuntimeException e) { + throw new SecurityException(e.getMessage(), e); } return true; } List tabComplete(CommandSender commandSender, String[] args) { - if (varArgType == null && args.length > arguments.length + subCommand.length) { - return null; - } if (guardChecker != null && guardChecker.guard(commandSender, GuardCheckType.TAB_COMPLETE, new String[0], null) != GuardResult.ALLOWED) { return null; } - int index = 0; - List 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; - index++; + if (commandPart == null) { + return null; } - int guardIndex = 0; - for (TypeMapper argument : arguments) { - String s = argsList.remove(0); - if (argsList.isEmpty()) { - if (guards[guardIndex] != null && guards[guardIndex].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, args.length - 1), s) != GuardResult.ALLOWED) { - return null; - } - return argument.tabCompletes(commandSender, Arrays.copyOf(args, args.length - 1), s); - } - try { - if (guards[guardIndex] != null && guards[guardIndex].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, index), s) != GuardResult.ALLOWED) { - return null; - } - if (argument.map(commandSender, Arrays.copyOf(args, index), s) == null) { - return null; - } - } catch (Exception e) { - return null; - } - index++; - guardIndex++; - } - if (varArgType != null && !argsList.isEmpty()) { - while (!argsList.isEmpty()) { - String s = argsList.remove(0); - if (argsList.isEmpty()) { - if (guards[guards.length - 1] != null && guards[guards.length - 1].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, args.length - 1), s) != GuardResult.ALLOWED) { - return null; - } - return arguments[arguments.length - 1].tabCompletes(commandSender, Arrays.copyOf(args, args.length - 1), s); - } - try { - if (guards[guards.length - 1] != null && guards[guards.length - 1].guard(commandSender, GuardCheckType.TAB_COMPLETE, Arrays.copyOf(args, index), s) != GuardResult.ALLOWED) { - return null; - } - if (arguments[arguments.length - 1].map(commandSender, Arrays.copyOf(args, index), s) == null) { - return null; - } - } catch (Exception e) { - return null; - } - index++; - } - } - return null; + List list = new ArrayList<>(); + commandPart.generateTabComplete(list, commandSender, args, 0); + return list; } } diff --git a/SpigotCore_Main/src/de/steamwar/command/TypeMapper.java b/SpigotCore_Main/src/de/steamwar/command/TypeMapper.java index dce1882..bc794fb 100644 --- a/SpigotCore_Main/src/de/steamwar/command/TypeMapper.java +++ b/SpigotCore_Main/src/de/steamwar/command/TypeMapper.java @@ -24,6 +24,9 @@ import org.bukkit.command.CommandSender; import java.util.List; public interface TypeMapper { + /** + * The CommandSender can be null! + */ default T map(CommandSender commandSender, String[] previousArguments, String s) { return map(previousArguments, s); } diff --git a/SpigotCore_Main/src/de/steamwar/comms/PacketIdManager.java b/SpigotCore_Main/src/de/steamwar/comms/PacketIdManager.java index f7b71e4..d4e85a2 100644 --- a/SpigotCore_Main/src/de/steamwar/comms/PacketIdManager.java +++ b/SpigotCore_Main/src/de/steamwar/comms/PacketIdManager.java @@ -27,6 +27,7 @@ public class PacketIdManager { public static final byte TABLIST_NAME = 0x02; public static final byte PREPARE_SCHEM = 0x03; public static final byte BAUMEMBER_UPDATE = 0x04; + public static final byte EXECUTE_COMMAND = 0x05; //0x1(X) Bungee Inventory public static final byte INVENTORY_PACKET = 0x10; public static final byte INVENTORY_CALLBACK_PACKET = 0x11; diff --git a/SpigotCore_Main/src/de/steamwar/comms/packets/ExecuteCommandPacket.java b/SpigotCore_Main/src/de/steamwar/comms/packets/ExecuteCommandPacket.java new file mode 100644 index 0000000..bede2b2 --- /dev/null +++ b/SpigotCore_Main/src/de/steamwar/comms/packets/ExecuteCommandPacket.java @@ -0,0 +1,47 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2021 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.comms.packets; + +import com.google.common.io.ByteArrayDataOutput; +import de.steamwar.comms.PacketIdManager; +import de.steamwar.sql.SteamwarUser; +import org.bukkit.entity.Player; + +public class ExecuteCommandPacket extends SpigotPacket { + + private final SteamwarUser user; + private final String command; + + public ExecuteCommandPacket(Player player, String command) { + this.user = SteamwarUser.get(player.getUniqueId()); + this.command = command; + } + + @Override + public int getName() { + return PacketIdManager.EXECUTE_COMMAND; + } + + @Override + public void writeVars(ByteArrayDataOutput out) { + out.writeInt(user.getId()); + out.writeUTF(command); + } +} diff --git a/SpigotCore_Main/src/de/steamwar/core/Core.java b/SpigotCore_Main/src/de/steamwar/core/Core.java index 6034638..8a47d30 100644 --- a/SpigotCore_Main/src/de/steamwar/core/Core.java +++ b/SpigotCore_Main/src/de/steamwar/core/Core.java @@ -19,10 +19,10 @@ package de.steamwar.core; +import com.comphenix.tinyprotocol.TinyProtocol; import de.steamwar.comms.BungeeReceiver; import de.steamwar.core.authlib.AuthlibInjector; import de.steamwar.core.events.ChattingEvent; -import de.steamwar.core.events.ChunkListener; import de.steamwar.core.events.PlayerJoinedEvent; import de.steamwar.core.events.WorldLoadEvent; import de.steamwar.message.Message; @@ -38,25 +38,31 @@ import java.util.logging.Level; public class Core extends JavaPlugin{ + private static final int VERSION; + public static final Message MESSAGE; private static Core instance; - private static final int version; - public static Message MESSAGE; static{ String packageName = Bukkit.getServer().getClass().getPackage().getName(); - if(packageName.contains("1_15")) - version = 15; + if(packageName.contains("1_18")) + VERSION = 18; + else if(packageName.contains("1_15")) + VERSION = 15; else if(packageName.contains("1_14")) - version = 14; + VERSION = 14; + else if(packageName.contains("1_12")) + VERSION = 12; else if(packageName.contains("1_10")) - version = 10; + VERSION = 10; else if(packageName.contains("1_9")) - version = 9; + VERSION = 9; else if(packageName.contains("1_8")) - version = 8; + VERSION = 8; else - version = 12; + VERSION = 18; + + MESSAGE = new Message("SpigotCore", Core.class.getClassLoader()); } private ErrorHandler errorHandler; @@ -75,20 +81,18 @@ public class Core extends JavaPlugin{ Bukkit.getPluginManager().registerEvents(new WorldLoadEvent(), this); getServer().getMessenger().registerIncomingPluginChannel(this, "sw:bridge", new BungeeReceiver()); getServer().getMessenger().registerOutgoingPluginChannel(this, "sw:bridge"); - ChunkListener.init(); AuthlibInjector.inject(); + TinyProtocol.init(); try { Bukkit.getLogger().log(Level.INFO, "Running on: " + new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("hostname").getInputStream())).readLine()); } catch (IOException e) { throw new SecurityException("Could not load Hostname", e); } - - MESSAGE = new Message("SpigotCore", getClassLoader()); } @Override public void onDisable() { - ChunkListener.protocol.close(); + TinyProtocol.instance.close(); errorHandler.unregister(); Statement.close(); } @@ -98,7 +102,7 @@ public class Core extends JavaPlugin{ } public static int getVersion(){ - return version; + return VERSION; } private static void setInstance(Core instance) { diff --git a/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java b/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java index 7853144..2b49104 100644 --- a/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java +++ b/SpigotCore_Main/src/de/steamwar/core/ErrorHandler.java @@ -141,6 +141,7 @@ public class ErrorHandler extends Handler { startsWith.add("Saving oversized chunk "); startsWith.add("Ignoring plugin channel"); startsWith.add("Ignoring incoming plugin"); + startsWith.add("This version of Minecraft is extremely outdated"); ignoreStartsWith = Collections.unmodifiableList(startsWith); List contains = new ArrayList<>(); diff --git a/SpigotCore_Main/src/de/steamwar/core/authlib/AuthlibInjector.java b/SpigotCore_Main/src/de/steamwar/core/authlib/AuthlibInjector.java index ace4936..0c9c188 100644 --- a/SpigotCore_Main/src/de/steamwar/core/authlib/AuthlibInjector.java +++ b/SpigotCore_Main/src/de/steamwar/core/authlib/AuthlibInjector.java @@ -27,7 +27,7 @@ public class AuthlibInjector { private AuthlibInjector() {} public static void inject() { - Class minecraftServerClass = Reflection.getClass("{nms}.MinecraftServer"); + Class minecraftServerClass = Reflection.getClass("{nms.server}.MinecraftServer"); Reflection.FieldAccessor gameProfile = Reflection.getField(minecraftServerClass, GameProfileRepository.class, 0); Object minecraftServer = Reflection.getTypedMethod(minecraftServerClass, "getServer", minecraftServerClass).invoke(null); gameProfile.set(minecraftServer, new SteamwarGameProfileRepository((YggdrasilGameProfileRepository) gameProfile.get(minecraftServer))); diff --git a/SpigotCore_Main/src/de/steamwar/core/events/ChunkListener.java b/SpigotCore_Main/src/de/steamwar/core/events/ChunkListener.java index fe285fe..9e51366 100644 --- a/SpigotCore_Main/src/de/steamwar/core/events/ChunkListener.java +++ b/SpigotCore_Main/src/de/steamwar/core/events/ChunkListener.java @@ -19,41 +19,14 @@ package de.steamwar.core.events; -import com.comphenix.tinyprotocol.Reflection; -import com.comphenix.tinyprotocol.TinyProtocol; -import de.steamwar.core.Core; import de.steamwar.core.CraftbukkitWrapper; -import de.steamwar.sql.SteamwarUser; -import io.netty.channel.Channel; import org.bukkit.entity.Player; +@Deprecated public class ChunkListener { private ChunkListener(){} - private static final Class mapChunkPacket = Reflection.getClass("{nms}.PacketPlayOutMapChunk"); - private static final Reflection.FieldAccessor fullChunk = Reflection.getField(mapChunkPacket, boolean.class, 0); - private static final Reflection.FieldAccessor chunkX = Reflection.getField(mapChunkPacket, int.class, 0); - private static final Reflection.FieldAccessor chunkZ = Reflection.getField(mapChunkPacket, int.class, 1); - - public static final TinyProtocol protocol = new TinyProtocol(Core.getInstance()){ - @Override - public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) { - if ( - !mapChunkPacket.isInstance(packet) || - !SteamwarUser.get(receiver.getUniqueId()).isBedrock() || - fullChunk.get(packet) - ) - return packet; - - CraftbukkitWrapper.impl.sendChunk(receiver, chunkX.get(packet), chunkZ.get(packet)); - return null; - } - }; - - public static void init(){ - //triggering - } - + @Deprecated public static void sendChunk(Player p, int chunkX, int chunkZ) { CraftbukkitWrapper.impl.sendChunk(p, chunkX, chunkZ); } diff --git a/SpigotCore_Main/src/de/steamwar/inventory/SchematicSelector.java b/SpigotCore_Main/src/de/steamwar/inventory/SchematicSelector.java index 83e8ef8..1c21ff3 100644 --- a/SpigotCore_Main/src/de/steamwar/inventory/SchematicSelector.java +++ b/SpigotCore_Main/src/de/steamwar/inventory/SchematicSelector.java @@ -20,6 +20,7 @@ package de.steamwar.inventory; import de.steamwar.core.Core; +import de.steamwar.sql.NodeMember; import de.steamwar.sql.SchematicNode; import de.steamwar.sql.SchematicType; import de.steamwar.sql.SteamwarUser; @@ -28,14 +29,13 @@ import org.bukkit.Material; import org.bukkit.entity.Player; import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.function.Consumer; public class SchematicSelector { + private static final Sorting[] all_sortings = Sorting.values(); + @Getter private final Player player; @Getter @@ -53,6 +53,11 @@ public class SchematicSelector { @Getter private boolean singleDirOpen; private boolean sdoTrigger = false; + @Getter + @Setter + private int depth = 0; + private Sorting sorting = Sorting.NAME; + private boolean invertSorting = false; public SchematicSelector(Player player, SelectorTarget target, Consumer callback) { this.player = player; @@ -76,16 +81,17 @@ public class SchematicSelector { } private void openList(SchematicNode parent) { - List nodes = filter.isFilter()?getFilteredSchematics():getSchematicList(parent); + List nodes = applySorting(filter.isFilter()?getFilteredSchematics():getSchematicList(parent)); if(sdoTrigger) { sdoTrigger = false; + openList(nodes.get(0)); return; } List> list = new ArrayList<>(); - if(parent != null) { + if(depth != 0) { list.add(new SWListInv.SWListEntry<>(new SWItem(Material.ARROW, Core.MESSAGE.parse("SCHEM_SELECTOR_BACK", player), clickType -> {}), null)); } @@ -118,6 +124,17 @@ public class SchematicSelector { inv.setItem(50, Material.CHEST, Core.MESSAGE.parse("SCHEM_SELECTOR_NEW_DIR", player), clickType -> createFolderIn(parent)); } inv.setItem(51, Material.NAME_TAG, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER", player), clickType -> openFilter()); + inv.setItem(47, sorting.mat, Core.MESSAGE.parse("SCHEM_SELECTOR_SORTING", player), Arrays.asList( + Core.MESSAGE.parse("SCHEM_SELECTOR_SORTING_CURRENT", player, sorting.parseName(player)), + Core.MESSAGE.parse("SCHEM_SELECTOR_SORTING_DIRECTION", player, Core.MESSAGE.parse(invertSorting?"SCHEM_SELECTOR_SORTING_DSC":"SCHEM_SELECTOR_SORTING_ASC", player)) + ), invertSorting, click -> { + if(click.isLeftClick()) { + cycleSorting(); + } else { + invertSorting = !invertSorting; + } + openList(parent); + }); injectable.onListRender(this, inv, parent); inv.open(); @@ -125,7 +142,32 @@ public class SchematicSelector { private void handleClick(SchematicNode node, SchematicNode parent) { if(node == null) { - openList(getParent(parent)); + depth--; + if(!singleDirOpen) { + if(NodeMember.getNodeMember(parent.getId(), user.getId()) != null) { + openList(null); + } else { + openList(getParent(parent)); + } + } else { + SchematicNode currentParent = parent; + boolean isMember = false; + do { + sdoTrigger = false; + if(NodeMember.getNodeMember(currentParent.getId(), user.getId()) != null) { + isMember = true; + } + currentParent = getParent(currentParent); + if(currentParent == null) + break; + getSchematicList(currentParent); + } while (sdoTrigger); + if(isMember || NodeMember.getNodeMember(parent.getId(), user.getId()) != null) { + openList(null); + } else { + openList(currentParent); + } + } return; } if(node.isDir()) { @@ -135,6 +177,7 @@ public class SchematicSelector { return; } filter.reset(); + depth++; openList(node); return; } @@ -142,6 +185,28 @@ public class SchematicSelector { callback.accept(node); } + private void cycleSorting() { + int next = sorting.ordinal() + 1; + if(next >= all_sortings.length) { + next = 0; + } + sorting = all_sortings[next]; + } + + private List applySorting(List nodes) { + if(sorting == Sorting.NAME && !invertSorting) { + return nodes; + } + + Comparator comparator = sorting.comparator; + if(invertSorting) { + comparator = comparator.reversed(); + } + + nodes.sort(comparator); + return nodes; + } + private SWListInv.SWListEntry renderItem(SchematicNode node) { Material m = SWItem.getMaterial(node.getItem()); @@ -163,10 +228,14 @@ public class SchematicSelector { SWAnvilInv inv = new SWAnvilInv(player, Core.MESSAGE.parse("SCHEM_SELECTOR_CREATE_DIR_TITLE", player)); inv.setItem(Material.CHEST); inv.setCallback(s -> { - if(injectable.onFolderCreate(this, s)) { - SchematicNode.createSchematicDirectory(user.getId(), s, parent==null?0:parent.getId()); - openList(parent); + if(!SchematicNode.invalidSchemName(new String[] {s})) { + if(injectable.onFolderCreate(this, s)) { + SchematicNode.createSchematicDirectory(user.getId(), s, parent==null?0:parent.getId()); + openList(parent); + } + return; } + player.closeInventory(); }); inv.open(); } @@ -201,7 +270,9 @@ public class SchematicSelector { SWAnvilInv swAnvilInv = new SWAnvilInv(player, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_ENTER_OWNER", player)); swAnvilInv.setItem(Material.PLAYER_HEAD); swAnvilInv.setCallback(s -> { - filter.setOwner(SteamwarUser.get(s).getId()); + if(SteamwarUser.get(s) != null) { + filter.setOwner(SteamwarUser.get(s).getId()); + } openFilter(); }); swAnvilInv.open(); @@ -211,7 +282,7 @@ public class SchematicSelector { inv.setItem(1, Material.PLAYER_HEAD, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_OWNER", player), ownerCallback); } else { SteamwarUser tUser = SteamwarUser.get(filter.getOwner()); - SWItem item = SWItem.getPlayerSkull(user.getUserName()); + SWItem item = SWItem.getPlayerSkull(tUser.getUserName()); item.setName(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_OWNER", player)); item.setEnchanted(true); item.setLore(Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_OWNER_SEARCH", player, tUser.getUserName()))); @@ -227,7 +298,7 @@ public class SchematicSelector { } else { List> types = new ArrayList<>(); SchematicType.values().forEach(schematicType -> { - types.add(new SWListInv.SWListEntry<>(new SWItem(SWItem.getMaterial("STONE_BUTTON"), "§e" + schematicType.name(), Collections.emptyList(), schematicType.fightType(), n -> {}), schematicType)); + types.add(new SWListInv.SWListEntry<>(new SWItem(schematicType.getMaterial(), "§e" + schematicType.name(), Collections.emptyList(), schematicType.fightType(), n -> {}), schematicType)); }); SWListInv listInv = new SWListInv<>(player, Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_SEL_TYPE", player), types, (clickType1, schematicType) -> { filter.setType(schematicType); @@ -238,9 +309,9 @@ public class SchematicSelector { }; if(filter.getType() == null) { - inv.setItem(2, SWItem.getMaterial("STONE_BUTTON"), Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE", player), schemTypeCallback); + inv.setItem(2, SchematicType.Normal.getMaterial(), Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE", player), schemTypeCallback); } else { - inv.setItem(2, SWItem.getMaterial("STONE_BUTTON"), Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE", player), Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE_SEARCH", player, filter.getType().name())), true, schemTypeCallback); + inv.setItem(2, filter.getType().getMaterial(), Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE", player), Collections.singletonList(Core.MESSAGE.parse("SCHEM_SELECTOR_FILTER_TYPE_SEARCH", player, filter.getType().name())), true, schemTypeCallback); } } @@ -266,11 +337,13 @@ public class SchematicSelector { inv.setItem(7, SWItem.getDye(1), Core.MESSAGE.parse("SCHEM_SELECTOR_CANCEL", player), clickType -> { filter.reset(); + depth = 0; openList(null); }); inv.setItem(8, SWItem.getDye(10), Core.MESSAGE.parse("SCHEM_SELECTOR_GO", player), clickType -> { filter.setFilter(true); injectable.onFilterApply(this); + depth = 0; openList(null); }); @@ -289,7 +362,7 @@ public class SchematicSelector { nodes.removeIf(node -> !node.isDir()); } if(target.target == Target.SCHEMATIC_TYPE) { - nodes.removeIf(node -> node.isDir() || !node.getType().equals(target.type.toDB())); + nodes.removeIf(node -> node.isDir() || !node.getSchemtype().equals(target.type)); } return nodes; } @@ -308,7 +381,12 @@ public class SchematicSelector { case SCHEMATIC_TYPE: nodes.addAll(SchematicNode.getAccessibleSchematicsOfTypeInParent(user.getId(), target.type.toDB(), parent==null?0:parent.getId())); if(target.rank >= 0) { - nodes.removeIf(node -> node.getRank() > target.rank); + nodes.removeIf(node -> { + if(node.isDir()) { + return false; + } + return node.getRank() > target.rank; + }); } break; default: @@ -316,7 +394,6 @@ public class SchematicSelector { } if(singleDirOpen && nodes.size() == 1 && nodes.get(0).isDir()) { - openList(nodes.get(0)); sdoTrigger = true; } return nodes; @@ -364,8 +441,8 @@ public class SchematicSelector { SCHEMATIC_NODE("SCHEM_SELECTOR_SCHEMATIC_NODE", true), SCHEMATIC_TYPE("SCHEM_SELECTOR_SCHEMATIC", false); - private String rawName; - private boolean dirs; + private final String rawName; + private final boolean dirs; private String getName(Player player) { return Core.MESSAGE.parse(rawName, player); @@ -426,4 +503,25 @@ public class SchematicSelector { PRIVATE_ONLY, PUBLIC_ONLY } + + @AllArgsConstructor + private enum Sorting { + NAME(Material.PAPER, "SCHEM_SELECTOR_SORTING_NAME", Comparator.comparing(SchematicNode::getName)), + TYPE(Material.CAULDRON, "SCHEM_SELECTOR_SORTING_TYPE", (o1, o2) -> { + if(o1.isDir() || o2.isDir()) { + return Boolean.compare(o1.isDir(), o2.isDir()); + } else { + return o1.getSchemtype().name().compareTo(o2.getSchemtype().name()); + } + }), + LAST_UPDATED(SWItem.getMaterial("WATCH"), "SCHEM_SELECTOR_SORTING_UPDATE", Comparator.comparing(SchematicNode::getLastUpdate)); + + private final Material mat; + private final String name; + private final Comparator comparator; + + private String parseName(Player player) { + return Core.MESSAGE.parse(name, player); + } + } } diff --git a/SpigotCore_Main/src/de/steamwar/scoreboard/SWScoreboard.java b/SpigotCore_Main/src/de/steamwar/scoreboard/SWScoreboard.java index 67cbeaa..9cde7bc 100644 --- a/SpigotCore_Main/src/de/steamwar/scoreboard/SWScoreboard.java +++ b/SpigotCore_Main/src/de/steamwar/scoreboard/SWScoreboard.java @@ -20,9 +20,9 @@ package de.steamwar.scoreboard; import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; import de.steamwar.core.Core; import de.steamwar.core.FlatteningWrapper; -import de.steamwar.core.events.ChunkListener; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -32,14 +32,14 @@ import java.util.Map; public class SWScoreboard { private SWScoreboard() {} - public static final Class scoreboardObjective = Reflection.getClass("{nms}.PacketPlayOutScoreboardObjective"); + public static final Class scoreboardObjective = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutScoreboardObjective"); private static final Reflection.FieldAccessor scoreboardName = Reflection.getField(scoreboardObjective, String.class, 0); private static final Reflection.FieldAccessor scoreboardAction = Reflection.getField(scoreboardObjective, int.class, 0); - private static final Class scoreboardDisplayEnum = Reflection.getClass("{nms}.IScoreboardCriteria$EnumScoreboardHealthDisplay"); + private static final Class scoreboardDisplayEnum = Reflection.getClass("{nms.world.scores.criteria}.IScoreboardCriteria$EnumScoreboardHealthDisplay"); private static final Reflection.FieldAccessor scoreboardDisplayType = Reflection.getField(scoreboardObjective, scoreboardDisplayEnum, 0); private static final Object displayTypeIntegers = scoreboardDisplayEnum.getEnumConstants()[0]; - public static final Class scoreboardScore = Reflection.getClass("{nms}.PacketPlayOutScoreboardScore"); + public static final Class scoreboardScore = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutScoreboardScore"); private static final Reflection.FieldAccessor scoreName = Reflection.getField(scoreboardScore, String.class, 0); private static final Reflection.FieldAccessor scoreScoreboardName = Reflection.getField(scoreboardScore, String.class, 1); private static final Reflection.FieldAccessor scoreValue = Reflection.getField(scoreboardScore, int.class, 0); @@ -52,7 +52,7 @@ public class SWScoreboard { private static final Object[] DISPLAY_SIDEBAR = new Object[2]; static { - Class scoreboardDisplayObjective = Reflection.getClass("{nms}.PacketPlayOutScoreboardDisplayObjective"); + Class scoreboardDisplayObjective = Reflection.getClass("{nms.network.protocol.game}.PacketPlayOutScoreboardDisplayObjective"); Reflection.FieldAccessor scoreboardDisplayName = Reflection.getField(scoreboardDisplayObjective, String.class, 0); Reflection.FieldAccessor scoreboardDisplaySlot = Reflection.getField(scoreboardDisplayObjective, int.class, 0); for(int id = 0; id < 2; id++) { @@ -72,16 +72,16 @@ public class SWScoreboard { Player player = scoreboard.getKey(); ScoreboardCallback callback = scoreboard.getValue(); - ChunkListener.protocol.sendPacket(player, DELETE_SCOREBOARD[toggle]); - ChunkListener.protocol.sendPacket(player, createSidebarPacket(callback.getTitle())); + TinyProtocol.instance.sendPacket(player, DELETE_SCOREBOARD[toggle]); + TinyProtocol.instance.sendPacket(player, createSidebarPacket(callback.getTitle())); for(Map.Entry score : callback.getData().entrySet()){ - ChunkListener.protocol.sendPacket(player, createScorePacket(score.getKey(), score.getValue())); + TinyProtocol.instance.sendPacket(player, createScorePacket(score.getKey(), score.getValue())); } Bukkit.getScheduler().runTaskLater(Core.getInstance(), () -> { if(!player.isOnline()) return; - ChunkListener.protocol.sendPacket(player, DISPLAY_SIDEBAR[toggle]); + TinyProtocol.instance.sendPacket(player, DISPLAY_SIDEBAR[toggle]); }, 2); } }, 10, 5); @@ -96,7 +96,7 @@ public class SWScoreboard { if(playerBoards.remove(player) == null || !player.isOnline()) return; - ChunkListener.protocol.sendPacket(player, DELETE_SCOREBOARD[toggle]); + TinyProtocol.instance.sendPacket(player, DELETE_SCOREBOARD[toggle]); } private static Object createSidebarPacket(String name){ diff --git a/SpigotCore_Main/src/de/steamwar/sql/Fight.java b/SpigotCore_Main/src/de/steamwar/sql/Fight.java index 3f9e2cd..eb5cd30 100644 --- a/SpigotCore_Main/src/de/steamwar/sql/Fight.java +++ b/SpigotCore_Main/src/de/steamwar/sql/Fight.java @@ -21,22 +21,23 @@ package de.steamwar.sql; import java.io.InputStream; import java.sql.Timestamp; +import java.time.Instant; import java.util.function.Consumer; public class Fight { private Fight(){} - private static final Statement create = new Statement("INSERT INTO Fight (GameMode, Server, Arena, StartTime, Duration, BlueLeader, RedLeader, BlueSchem, RedSchem, Win, WinCondition) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + private static final Statement create = new Statement("INSERT INTO Fight (GameMode, Server, StartTime, Duration, BlueLeader, RedLeader, BlueSchem, RedSchem, Win, WinCondition, ReplayLock) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); private static final Statement lastId = new Statement("SELECT LAST_INSERT_ID() AS FightID"); private static final Statement getReplay = new Statement("SELECT Replay FROM Fight WHERE FightID = ?"); private static final Statement setReplay = new Statement("UPDATE Fight SET Replay = ? WHERE FightID = ?"); - public static int create(String gamemode, String arena, Timestamp starttime, int duration, int blueleader, int redleader, Integer blueschem, Integer redschem, int win, String wincondition){ - return create(gamemode, arena, null, starttime, duration, blueleader, redleader, blueschem, redschem, win, wincondition); + public static int create(String gamemode, String server, Timestamp starttime, int duration, int blueleader, int redleader, Integer blueschem, Integer redschem, int win, String wincondition){ + return create(gamemode, server, starttime, duration, blueleader, redleader, blueschem, redschem, win, wincondition, Timestamp.from(Instant.now())); } - public static int create(String gamemode, String server, String arena, Timestamp starttime, int duration, int blueleader, int redleader, Integer blueschem, Integer redschem, int win, String wincondition){ - create.update(gamemode, server, arena, starttime, duration, blueleader, redleader, blueschem, redschem, win, wincondition); + public static int create(String gamemode, String server, Timestamp starttime, int duration, int blueleader, int redleader, Integer blueschem, Integer redschem, int win, String wincondition, Timestamp replayLock){ + create.update(gamemode, server, starttime, duration, blueleader, redleader, blueschem, redschem, win, wincondition, replayLock); return lastId.select(rs -> { rs.next(); return rs.getInt("FightID"); diff --git a/SpigotCore_Main/src/de/steamwar/sql/Punishment.java b/SpigotCore_Main/src/de/steamwar/sql/Punishment.java new file mode 100644 index 0000000..85ec258 --- /dev/null +++ b/SpigotCore_Main/src/de/steamwar/sql/Punishment.java @@ -0,0 +1,141 @@ +/* + 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.sql; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.function.Consumer; + +public class Punishment { + + private static final SQL.Statement getPunishment = new SQL.Statement("SELECT * FROM Punishments WHERE UserId = ? AND Type = ? ORDER BY PunishmentId DESC LIMIT 1"); + private static final SQL.Statement getPunishments = new SQL.Statement("SELECT * FROM Punishments WHERE PunishmentId IN (SELECT MAX(PunishmentId) FROM Punishments WHERE UserId = ? GROUP BY Type)"); + + public static Punishment getPunishmentOfPlayer(int user, PunishmentType type) { + return getPunishment.select(rs -> { + if (rs.next()) + return new Punishment(rs); + return null; + }, user, type.name()); + } + + public static Map getPunishmentsOfPlayer(int user) { + return getPunishments.select(rs -> { + Map punishments = new HashMap<>(); + while (rs.next()) + punishments.put(PunishmentType.valueOf(rs.getString("Type")), new Punishment(rs)); + return punishments; + }, user); + } + + public static boolean isPunished(SteamwarUser user, Punishment.PunishmentType type, Consumer callback) { + Punishment punishment = Punishment.getPunishmentOfPlayer(user.getId(), type); + if(punishment == null || punishment.isCurrent()) { + return false; + } else { + callback.accept(punishment); + return true; + } + } + + private final Timestamp startTime; + private Timestamp endTime; + private final PunishmentType type; + private final int user; + private final int id; + private String reason; + private final int punisher; + private boolean perma; + + private Punishment(ResultSet set) throws SQLException { + user = set.getInt("UserId"); + reason = set.getString("Reason"); + type = PunishmentType.valueOf(set.getString("Type")); + startTime = set.getTimestamp("StartTime"); + endTime = set.getTimestamp("EndTime"); + punisher = set.getInt("Punisher"); + perma = set.getBoolean("Perma"); + id = set.getInt("PunishmentId"); + } + + public Timestamp getStartTime() { + return startTime; + } + + public Timestamp getEndTime() { + return endTime; + } + + public PunishmentType getType() { + return type; + } + + public int getUser() { + return user; + } + + public String getReason() { + return reason; + } + + public int getPunisher() { + return punisher; + } + + public boolean isPerma() { + return perma; + } + + public String getBantime(Timestamp endTime, boolean perma) { + if (perma) { + return "permanent"; + } else { + return endTime.toLocalDateTime().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")); + } + } + + public boolean isCurrent() { + return isPerma() || getEndTime().after(new Date()); + } + + @AllArgsConstructor + @Getter + public enum PunishmentType { + Ban(false, "BAN_TEAM", "BAN_PERMA", "BAN_UNTIL", "UNBAN_ERROR", "UNBAN"), + Mute( false, "MUTE_TEAM", "MUTE_PERMA", "MUTE_UNTIL", "UNMUTE_ERROR", "UNMUTE"), + NoSchemReceiving(false, "NOSCHEMRECEIVING_TEAM", "NOSCHEMRECEIVING_PERMA", "NOSCHEMRECEIVING_UNTIL", "UNNOSCHEMRECEIVING_ERROR", "UNNOSCHEMRECEIVING"), + NoSchemSharing(false, "NOSCHEMSHARING_TEAM", "NOSCHEMSHARING_PERMA", "NOSCHEMSHARING_UNTIL", "UNNOSCHEMSHARING_ERROR", "UNNOSCHEMSHARING"), + NoSchemSubmitting(true, "NOSCHEMSUBMITTING_TEAM", "NOSCHEMSUBMITTING_PERMA", "NOSCHEMSUBMITTING_UNTIL", "UNNOSCHEMSUBMITTING_ERROR", "UNNOSCHEMSUBMITTING"), + NoDevServer(true, "NODEVSERVER_TEAM", "NODEVSERVER_PERMA", "NODEVSERVER_UNTIL", "UNNODEVSERVER_ERROR", "UNNODEVSERVER"); + + private final boolean needsAdmin; + private final String teamMessage; + private final String playerMessagePerma; + private final String playerMessageUntil; + private final String usageNotPunished; + private final String unpunishmentMessage; + } +} diff --git a/SpigotCore_Main/src/de/steamwar/sql/SchematicNode.java b/SpigotCore_Main/src/de/steamwar/sql/SchematicNode.java index fcb49de..32c3d51 100644 --- a/SpigotCore_Main/src/de/steamwar/sql/SchematicNode.java +++ b/SpigotCore_Main/src/de/steamwar/sql/SchematicNode.java @@ -33,6 +33,7 @@ import java.sql.SQLException; import java.sql.Timestamp; import java.time.Instant; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import java.util.zip.GZIPInputStream; @@ -487,13 +488,14 @@ public class SchematicNode { StringBuilder builder = new StringBuilder(getName()); SchematicNode currentNode = this; if (currentNode.isDir()) builder.append("/"); - while (currentNode.getParentNode() != null) { + final Set nodeMembers = NodeMember.getSchematics(user.getId()); + AtomicInteger i = new AtomicInteger(); + i.set(currentNode.getId()); + while (currentNode.getParentNode() != null && nodeMembers.stream().noneMatch(nodeMember -> nodeMember.getNode() == i.get())) { currentNode = currentNode.getParentNode(); + i.set(currentNode.getId()); builder.insert(0, split) .insert(0, currentNode.getName()); - if (currentNode.getMembers().stream().anyMatch(member -> member.getMember() == user.getId())) { - break; - } } return builder.toString(); } @@ -505,9 +507,6 @@ public class SchematicNode { } public void delete() { - if (isDir()) { - getSchematicNodeInNode(getId()).forEach(SchematicNode::delete); - } deleteNode.update(id); } @@ -601,4 +600,28 @@ public class SchematicNode { list.remove("//copy"); return list; } + + private static final List FORBIDDEN_NAMES = Collections.unmodifiableList(Arrays.asList("public")); + public static boolean invalidSchemName(String[] layers) { + for (String layer : layers) { + if (layer.isEmpty()) { + return true; + } + if (layer.contains("/") || + layer.contains("\\") || + layer.contains("<") || + layer.contains(">") || + layer.contains("^") || + layer.contains("°") || + layer.contains("'") || + layer.contains("\"") || + layer.contains(" ")) { + return true; + } + if(FORBIDDEN_NAMES.contains(layer.toLowerCase())) { + return true; + } + } + return false; + } } diff --git a/SpigotCore_Main/src/de/steamwar/sql/SchematicType.java b/SpigotCore_Main/src/de/steamwar/sql/SchematicType.java index 5fe5715..5b5bd0d 100644 --- a/SpigotCore_Main/src/de/steamwar/sql/SchematicType.java +++ b/SpigotCore_Main/src/de/steamwar/sql/SchematicType.java @@ -20,6 +20,8 @@ package de.steamwar.sql; import de.steamwar.core.Core; +import de.steamwar.inventory.SWItem; +import org.bukkit.Material; import org.bukkit.configuration.file.YamlConfiguration; import java.io.File; @@ -27,7 +29,7 @@ import java.util.*; import java.util.stream.Collectors; public class SchematicType { - public static final SchematicType Normal = new SchematicType("Normal", "", Type.NORMAL, null); //Has to stay publicly availible + public static final SchematicType Normal = new SchematicType("Normal", "", Type.NORMAL, null, SWItem.getMaterial("STONE_BUTTON")); //Has to stay publicly availible private static final Map fromDB; private static final List types; @@ -54,14 +56,15 @@ public class SchematicType { continue; SchematicType checktype = null; + Material material = SWItem.getMaterial(config.getString("Schematic.Material", "STONE_BUTTON")); if(!config.getStringList("CheckQuestions").isEmpty()) { - checktype = new SchematicType("C" + type, "C" + shortcut, Type.CHECK_TYPE, null); + checktype = new SchematicType("C" + type, "C" + shortcut, Type.CHECK_TYPE, null, material); tmpTypes.add(checktype); tmpFromDB.put(checktype.toDB(), checktype); } - SchematicType current = new SchematicType(type, shortcut, config.isConfigurationSection("Server") ? Type.FIGHT_TYPE : Type.NORMAL, checktype); + SchematicType current = new SchematicType(type, shortcut, config.isConfigurationSection("Server") ? Type.FIGHT_TYPE : Type.NORMAL, checktype, material); tmpTypes.add(current); tmpFromDB.put(type.toLowerCase(), current); } @@ -75,12 +78,14 @@ public class SchematicType { private final String kuerzel; private final Type type; private final SchematicType checkType; + private final Material material; - private SchematicType(String name, String kuerzel, Type type, SchematicType checkType){ + private SchematicType(String name, String kuerzel, Type type, SchematicType checkType, Material material){ this.name = name; this.kuerzel = kuerzel; this.type = type; this.checkType = checkType; + this.material = material; } public boolean isAssignable(){ @@ -111,6 +116,10 @@ public class SchematicType { return kuerzel; } + public Material getMaterial() { + return material; + } + public String toDB(){ return name.toLowerCase(); } diff --git a/SpigotCore_Main/src/de/steamwar/sql/SteamwarUser.java b/SpigotCore_Main/src/de/steamwar/sql/SteamwarUser.java index f5b42f8..05c3d8d 100644 --- a/SpigotCore_Main/src/de/steamwar/sql/SteamwarUser.java +++ b/SpigotCore_Main/src/de/steamwar/sql/SteamwarUser.java @@ -25,7 +25,9 @@ import org.bukkit.entity.Player; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; public class SteamwarUser { diff --git a/SpigotCore_Main/src/plugin.yml b/SpigotCore_Main/src/plugin.yml index 8eee87b..115baf6 100644 --- a/SpigotCore_Main/src/plugin.yml +++ b/SpigotCore_Main/src/plugin.yml @@ -7,4 +7,5 @@ softdepend: - WorldEdit main: de.steamwar.core.Core -commands: \ No newline at end of file +libraries: + - mysql:mysql-connector-java:5.1.49 \ No newline at end of file diff --git a/SpigotCore_Main/testsrc/de/steamwar/TestCommandSender.java b/SpigotCore_Main/testsrc/de/steamwar/TestCommandSender.java new file mode 100644 index 0000000..02760e4 --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/TestCommandSender.java @@ -0,0 +1,122 @@ +/* + * 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; + +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; + +import java.util.Set; + +public class TestCommandSender implements CommandSender { + + @Override + public void sendMessage(String s) { + + } + + @Override + public void sendMessage(String[] strings) { + + } + + @Override + public Server getServer() { + return null; + } + + @Override + public String getName() { + return null; + } + + @Override + public Spigot spigot() { + return null; + } + + @Override + public boolean isPermissionSet(String s) { + return false; + } + + @Override + public boolean isPermissionSet(Permission permission) { + return false; + } + + @Override + public boolean hasPermission(String s) { + return false; + } + + @Override + public boolean hasPermission(Permission permission) { + return false; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b) { + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin) { + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String s, boolean b, int i) { + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, int i) { + return null; + } + + @Override + public void removeAttachment(PermissionAttachment permissionAttachment) { + + } + + @Override + public void recalculatePermissions() { + + } + + @Override + public Set getEffectivePermissions() { + return null; + } + + @Override + public boolean isOp() { + return false; + } + + @Override + public void setOp(boolean b) { + + } +} diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/ExecutionIdentifier.java b/SpigotCore_Main/testsrc/de/steamwar/command/ExecutionIdentifier.java new file mode 100644 index 0000000..e4ed591 --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/command/ExecutionIdentifier.java @@ -0,0 +1,41 @@ +/* + * 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; + +public class ExecutionIdentifier extends RuntimeException { + public ExecutionIdentifier() { + } + + public ExecutionIdentifier(String message) { + super(message); + } + + public ExecutionIdentifier(String message, Throwable cause) { + super(message, cause); + } + + public ExecutionIdentifier(Throwable cause) { + super(cause); + } + + public ExecutionIdentifier(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommand.java b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommand.java new file mode 100644 index 0000000..79f2844 --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommand.java @@ -0,0 +1,34 @@ +/* + * 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; + +public class SimpleCommand extends SWCommand { + + public SimpleCommand() { + super(true, "simple"); + } + + @Register + public void execute(CommandSender sender) { + throw new ExecutionIdentifier("Simple execute without any parameters"); + } +} diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandPartTest.java b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandPartTest.java new file mode 100644 index 0000000..08dccf7 --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandPartTest.java @@ -0,0 +1,289 @@ +/* + * 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 de.steamwar.TestCommandSender; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class SimpleCommandPartTest { + + private CommandPart stringCommandPart; + private CommandPart intCommandPart; + private CommandPart chainedCommandPart; + private CommandPart varArgCommandPart; + + private CommandPart simpleGuardPart; + + private CommandPart optionalCommandPart; + + private CommandPart optionalCommandPartMultipleNext; + + @Before + public void setUp() throws Exception { + stringCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, null, null, GuardCheckType.COMMAND); + intCommandPart = new CommandPart(SWCommandUtils.MAPPER_FUNCTIONS.get("int"), null, null, null, GuardCheckType.COMMAND); + + chainedCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, null, null, GuardCheckType.COMMAND); + chainedCommandPart.setNext(new CommandPart(SWCommandUtils.MAPPER_FUNCTIONS.get("int"), null, null, null, GuardCheckType.COMMAND)); + + varArgCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, String.class, null, GuardCheckType.COMMAND); + + simpleGuardPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), (commandSender, guardCheckType, previousArguments, s) -> s.equals("hello") ? GuardResult.DENIED : GuardResult.ALLOWED, null, null, GuardCheckType.COMMAND); + + optionalCommandPart = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, null, "hello", GuardCheckType.COMMAND); + optionalCommandPart.setNext(new CommandPart(SWCommandUtils.createMapper("hello2", "world2"), null, null, null, GuardCheckType.COMMAND)); + + optionalCommandPartMultipleNext = new CommandPart(SWCommandUtils.createMapper("hello", "world"), null, null, "hello", GuardCheckType.COMMAND); + CommandPart next = new CommandPart(SWCommandUtils.createMapper("hello2", "world2"), null, null, null, GuardCheckType.COMMAND); + next.setNext(new CommandPart(SWCommandUtils.createMapper("hello3", "world3"), null, null, null, GuardCheckType.COMMAND)); + optionalCommandPartMultipleNext.setNext(next); + } + + @Test + public void testCommandPartTabCompleteNoArguments() { + List tabComplete = new ArrayList<>(); + stringCommandPart.generateTabComplete(tabComplete, new TestCommandSender(), new String[]{""}, 0); + assertThat(tabComplete.size(), is(2)); + assertThat(tabComplete.get(0), is("hello")); + assertThat(tabComplete.get(1), is("world")); + } + + @Test(expected = CommandParseException.class) + public void testCommandExecuteInvalidArgument() { + stringCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{""}, 0); + } + + @Test + public void testCommandExecuteValidArgument() { + List argumentArray = new ArrayList<>(); + stringCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello"}, 0); + assertThat(argumentArray.size(), is(1)); + assertThat(argumentArray.get(0), instanceOf(String.class)); + assertThat(argumentArray.get(0), is("hello")); + } + + @Test + public void testCommandExecuteValidOtherArgument() { + List argumentArray = new ArrayList<>(); + stringCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"world"}, 0); + assertThat(argumentArray.size(), is(1)); + assertThat(argumentArray.get(0), instanceOf(String.class)); + assertThat(argumentArray.get(0), is("world")); + } + + @Test(expected = CommandParseException.class) + public void testCommandExecuteNonNumberArgument() { + intCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{"world"}, 0); + } + + @Test + public void testCommandExecuteValidNumberArgument() { + List argumentArray = new ArrayList<>(); + intCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"0"}, 0); + assertThat(argumentArray.size(), is(1)); + assertThat(argumentArray.get(0), instanceOf(int.class)); + assertThat(argumentArray.get(0), is(0)); + } + + @Test + public void testChainedCommandExecuteValidArgument() { + List argumentArray = new ArrayList<>(); + chainedCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello", "0"}, 0); + assertThat(argumentArray.size(), is(2)); + assertThat(argumentArray.get(0), instanceOf(String.class)); + assertThat(argumentArray.get(0), is("hello")); + assertThat(argumentArray.get(1), instanceOf(int.class)); + assertThat(argumentArray.get(1), is(0)); + } + + @Test + public void testChainedCommandTabComplete() { + List tabCompletes = new ArrayList<>(); + chainedCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{""}, 0); + assertThat(tabCompletes.size(), is(2)); + assertThat(tabCompletes.get(0), is("hello")); + assertThat(tabCompletes.get(1), is("world")); + } + + @Test + public void testChainedCommandTabCompleteOther() { + List tabCompletes = new ArrayList<>(); + chainedCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello", ""}, 0); + assertThat(tabCompletes.size(), is(0)); + } + + @Test + public void testVarArgsCommandTabComplete() { + List tabCompletes = new ArrayList<>(); + varArgCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello"}, 0); + assertThat(tabCompletes.size(), is(2)); + assertThat(tabCompletes.get(0), is("hello")); + assertThat(tabCompletes.get(1), is("world")); + } + + @Test + public void testVarArgsCommandTabCompleteDeeper() { + List tabCompletes = new ArrayList<>(); + varArgCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello", "world", "hello", "world"}, 0); + System.out.println(tabCompletes); + assertThat(tabCompletes.size(), is(2)); + assertThat(tabCompletes.get(0), is("hello")); + assertThat(tabCompletes.get(1), is("world")); + } + + @Test + public void testVarArgsCommandArgumentParsing() { + List argumentArray = new ArrayList<>(); + varArgCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello"}, 0); + assertThat(argumentArray.size(), is(1)); + assertThat(argumentArray.get(0), instanceOf(String[].class)); + assertThat((String[]) argumentArray.get(0), arrayWithSize(1)); + assertThat((String[]) argumentArray.get(0), is(new String[]{"hello"})); + } + + @Test + public void testVarArgsCommandArgumentParsingDeeper() { + List argumentArray = new ArrayList<>(); + varArgCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello", "world", "hello", "world"}, 0); + assertThat(argumentArray.size(), is(1)); + assertThat(argumentArray.get(0), instanceOf(String[].class)); + assertThat((String[]) argumentArray.get(0), arrayWithSize(4)); + assertThat((String[]) argumentArray.get(0), is(new String[]{"hello", "world", "hello", "world"})); + } + + @Test + public void testGuardCommandExecute() { + List argumentArray = new ArrayList<>(); + simpleGuardPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello"}, 0); + assertThat(argumentArray.size(), is(1)); + } + + @Test(expected = CommandNoHelpException.class) + public void testGuardGuardCheck() { + simpleGuardPart.guardCheck(new TestCommandSender(), new String[]{"hello"}, 0); + } + + @Test + public void testGuardCommandExecuteValid() { + List argumentArray = new ArrayList<>(); + simpleGuardPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"world"}, 0); + assertThat(argumentArray.size(), is(1)); + } + + @Test + public void testGuardGuardCheckValid() { + boolean guardResult = simpleGuardPart.guardCheck(new TestCommandSender(), new String[]{"world"}, 0); + assertThat(guardResult, is(true)); + } + + @Test + public void testOptionalCommandPartTabComplete() { + List tabCompletes = new ArrayList<>(); + optionalCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{""}, 0); + assertThat(tabCompletes.size(), is(4)); + assertThat(tabCompletes.get(0), is("hello")); + assertThat(tabCompletes.get(1), is("world")); + assertThat(tabCompletes.get(2), is("hello2")); + assertThat(tabCompletes.get(3), is("world2")); + } + + @Test + public void testOptionalCommandPartTabCompleteSecond() { + List tabCompletes = new ArrayList<>(); + optionalCommandPart.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello", ""}, 0); + assertThat(tabCompletes.size(), is(2)); + assertThat(tabCompletes.get(0), is("hello2")); + assertThat(tabCompletes.get(1), is("world2")); + } + + @Test(expected = CommandParseException.class) + public void testOptionalCommandPartExecution() { + optionalCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{""}, 0); + } + + @Test + public void testOptionalCommandPartExecutionValid() { + List argumentArray = new ArrayList<>(); + optionalCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"hello2"}, 0); + assertThat(argumentArray.size(), is(2)); + assertThat(argumentArray.get(0), is("hello")); + assertThat(argumentArray.get(1), is("hello2")); + } + + @Test(expected = CommandParseException.class) + public void testOptionalCommandPartExecutionInvalid() { + optionalCommandPart.generateArgumentArray(new ArrayList<>(), new TestCommandSender(), new String[]{"hello"}, 0); + } + + @Test + public void testOptionalCommandPartExecutionFullyValid() { + List argumentArray = new ArrayList<>(); + optionalCommandPart.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"world", "hello2"}, 0); + assertThat(argumentArray.size(), is(2)); + assertThat(argumentArray.get(0), is("world")); + assertThat(argumentArray.get(1), is("hello2")); + } + + @Test + public void testOptionalCommandPartExecutionMultipleNext() { + List argumentArray = new ArrayList<>(); + optionalCommandPartMultipleNext.generateArgumentArray(argumentArray, new TestCommandSender(), new String[]{"world", "hello2", "hello3"}, 0); + assertThat(argumentArray.size(), is(3)); + assertThat(argumentArray.get(0), is("world")); + assertThat(argumentArray.get(1), is("hello2")); + assertThat(argumentArray.get(2), is("hello3")); + } + + @Test + public void testOptionalCommandPartExecutionMultipleTabComplete() { + List tabCompletes = new ArrayList<>(); + optionalCommandPartMultipleNext.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{""}, 0); + assertThat(tabCompletes.size(), is(4)); + assertThat(tabCompletes.get(0), is("hello")); + assertThat(tabCompletes.get(1), is("world")); + assertThat(tabCompletes.get(2), is("hello2")); + assertThat(tabCompletes.get(3), is("world2")); + + tabCompletes = new ArrayList<>(); + optionalCommandPartMultipleNext.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello", ""}, 0); + assertThat(tabCompletes.size(), is(2)); + assertThat(tabCompletes.get(0), is("hello2")); + assertThat(tabCompletes.get(1), is("world2")); + + tabCompletes = new ArrayList<>(); + optionalCommandPartMultipleNext.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"hello", "world2", ""}, 0); + assertThat(tabCompletes.size(), is(2)); + assertThat(tabCompletes.get(0), is("hello3")); + assertThat(tabCompletes.get(1), is("world3")); + + tabCompletes = new ArrayList<>(); + optionalCommandPartMultipleNext.generateTabComplete(tabCompletes, new TestCommandSender(), new String[]{"world2", ""}, 0); + assertThat(tabCompletes.size(), is(2)); + assertThat(tabCompletes.get(0), is("hello3")); + assertThat(tabCompletes.get(1), is("world3")); + } +} diff --git a/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java new file mode 100644 index 0000000..c18728e --- /dev/null +++ b/SpigotCore_Main/testsrc/de/steamwar/command/SimpleCommandTest.java @@ -0,0 +1,61 @@ +/* + * 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 de.steamwar.TestCommandSender; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class SimpleCommandTest { + + private SimpleCommand simpleCommand; + + @Before + public void setUp() throws Exception { + simpleCommand = new SimpleCommand(); + } + + @Test + public void testCommandParsing() { + try { + simpleCommand.execute(new TestCommandSender(), "", new String[]{}); + } catch (CommandFrameworkException commandFrameworkException) { + if (commandFrameworkException.getCause().getCause() instanceof ExecutionIdentifier) { + ExecutionIdentifier executionIdentifier = (ExecutionIdentifier) commandFrameworkException.getCause().getCause(); + assertThat(executionIdentifier.getMessage(), is("Simple execute without any parameters")); + return; + } + } + assert false; + } + + @Test + public void testUnknownCommandParsing() { + try { + simpleCommand.execute(new TestCommandSender(), "", new String[]{"unknown"}); + } catch (SecurityException securityException) { + securityException.printStackTrace(); + assert false; + } + } +} diff --git a/build.gradle b/build.gradle index 2230e6a..52eae83 100644 --- a/build.gradle +++ b/build.gradle @@ -74,6 +74,14 @@ allprojects { maven { url = uri("https://repo.codemc.io/repository/maven-snapshots/") } + + maven { + url = uri('https://hub.spigotmc.org/nexus/content/repositories/snapshots/') + } + + maven { + url = uri('https://libraries.minecraft.net') + } } } @@ -85,6 +93,7 @@ dependencies { implementation project(":SpigotCore_12") implementation project(":SpigotCore_14") implementation project(":SpigotCore_15") + implementation project(":SpigotCore_18") } task buildProject { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 099d7b2..086800c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ #Wed May 05 10:45:33 CEST 2021 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 44cbeeb..0bd9de2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,6 +20,7 @@ rootProject.name = 'SpigotCore' include 'SpigotCore_Main' +include 'SpigotCore_18' include 'SpigotCore_15' include 'SpigotCore_14' include 'SpigotCore_12' diff --git a/steamwarci.yml b/steamwarci.yml index d3e2564..e630fae 100644 --- a/steamwarci.yml +++ b/steamwarci.yml @@ -3,6 +3,7 @@ build: - "cp ~/gradle.properties ." - "chmod u+x build.gradle" - "./gradlew buildProject" + - "./gradlew test" artifacts: "/binarys/spigotcore.jar": "build/libs/spigotcore.jar"