Compare commits
	
		
			2 Commits
		
	
	
		
			decf302851
			...
			e7b528718c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e7b528718c | |||
| d411618e63 | 
| @@ -25,7 +25,6 @@ import java.util.function.Function; | ||||
|  */ | ||||
| public class PandalibBungeePermissions implements Listener { | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Registers event listener to redirect permission checks to {@code pandalib-permissions}. | ||||
|      * @param bungeePlugin a BungeeCord plugin. | ||||
| @@ -35,6 +34,8 @@ public class PandalibBungeePermissions implements Listener { | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private PandalibBungeePermissions() {} | ||||
|  | ||||
|     /** | ||||
|      * Event handler called when a plugin asks if a player has a permission. | ||||
|      * @param event the permission check event. | ||||
|   | ||||
| @@ -43,4 +43,7 @@ public class PandaLibBungee { | ||||
|     public static Plugin getPlugin() { | ||||
|         return plugin; | ||||
|     } | ||||
|  | ||||
|     private PandaLibBungee() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -35,4 +35,11 @@ public class BungeeBackupConfig { | ||||
|      * A list of ignored files or directory in the workdir to exclude from the backup. | ||||
|      */ | ||||
|     public List<String> workdirIgnoreList = new ArrayList<>(); | ||||
|  | ||||
|     /** | ||||
|      * Creates a new {@link BungeeBackupConfig}. | ||||
|      */ | ||||
|     public BungeeBackupConfig() { | ||||
|  | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,8 @@ import net.kyori.adventure.text.Component; | ||||
| import net.kyori.adventure.text.ComponentBuilder; | ||||
| import net.kyori.adventure.text.ComponentLike; | ||||
| import net.kyori.adventure.text.TextComponent; | ||||
| import net.kyori.adventure.text.TranslationArgument; | ||||
| import net.kyori.adventure.text.TranslationArgumentLike; | ||||
| import net.kyori.adventure.text.event.ClickEvent; | ||||
| import net.kyori.adventure.text.event.HoverEvent; | ||||
| import net.kyori.adventure.text.event.HoverEventSource; | ||||
| @@ -972,21 +974,38 @@ public abstract sealed class Chat extends ChatStatic implements HoverEventSource | ||||
|  | ||||
|  | ||||
|  | ||||
|     /* package */ static ComponentLike filterObjToComponentLike(Object v) { | ||||
|         return switch (v) { | ||||
|             case BaseComponent[] baseComponents -> toAdventure(baseComponents); | ||||
|             case BaseComponent baseComponent -> toAdventure(baseComponent); | ||||
|             case ComponentLike componentLike -> componentLike; | ||||
|             case null, default -> Component.text(Objects.toString(v)); | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     /* package */ static ComponentLike[] filterObjToComponentLike(Object[] values) { | ||||
|         if (values == null) | ||||
|             return null; | ||||
|         ComponentLike[] ret = new ComponentLike[values.length]; | ||||
|         for (int i = 0; i < values.length; i++) { | ||||
|             ret[i] = filterObjToComponentLike(values[i]); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /* package */ static TranslationArgumentLike[] filterObjToTranslationArgumentLike(Object[] values) { | ||||
|         if (values == null) | ||||
|             return null; | ||||
|         TranslationArgumentLike[] ret = new TranslationArgumentLike[values.length]; | ||||
|         for (int i = 0; i < values.length; i++) { | ||||
|             Object v = values[i]; | ||||
|             if (v instanceof BaseComponent[]) | ||||
|                 ret[i] = toAdventure((BaseComponent[]) v); | ||||
|             else if (v instanceof BaseComponent) | ||||
|                 ret[i] = toAdventure((BaseComponent) v); | ||||
|             else if (v instanceof ComponentLike) | ||||
|                 ret[i] = (ComponentLike) v; | ||||
|             if (v instanceof Number n) | ||||
|                 ret[i] = TranslationArgument.numeric(n); | ||||
|             else if (v instanceof Boolean b) | ||||
|                 ret[i] = TranslationArgument.bool(b); | ||||
|             else | ||||
|                 ret[i] = Component.text(Objects.toString(v)); | ||||
|                 ret[i] = TranslationArgument.component(filterObjToComponentLike(values[i])); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import org.jetbrains.annotations.NotNull; | ||||
|  * A custom gradient with at least 2 colors in it. | ||||
|  */ | ||||
| public class ChatColorGradient { | ||||
|  | ||||
|     private record GradientColor( | ||||
|             float location, | ||||
|             TextColor color | ||||
| @@ -22,6 +23,11 @@ public class ChatColorGradient { | ||||
|  | ||||
|     private final List<GradientColor> colors = new ArrayList<>(); | ||||
|  | ||||
|     /** | ||||
|      * Create the custom gradient. | ||||
|      */ | ||||
|     public ChatColorGradient() {} | ||||
|  | ||||
|     /** | ||||
|      * Put a specific color at a specific location in the gradient. | ||||
|      * @param gradientLocation the location in the gradient. | ||||
| @@ -43,7 +49,7 @@ public class ChatColorGradient { | ||||
|         if (colors.isEmpty()) | ||||
|             throw new IllegalStateException("Must define at least one color in this ChatColorGradient instance."); | ||||
|         if (colors.size() == 1) | ||||
|             return colors.get(0).color(); | ||||
|             return colors.getFirst().color(); | ||||
|  | ||||
|         int i = 0; | ||||
|         for (; i < colors.size(); i++) { | ||||
| @@ -54,7 +60,7 @@ public class ChatColorGradient { | ||||
|         if (i == 0) | ||||
|             return colors.get(i).color(); | ||||
|         if (i == colors.size()) | ||||
|             return colors.get(colors.size() - 1).color(); | ||||
|             return colors.getLast().color(); | ||||
|  | ||||
|         int p = i - 1; | ||||
|         float pLoc = colors.get(p).location(); | ||||
|   | ||||
| @@ -293,4 +293,7 @@ public class ChatColorUtil { | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     private ChatColorUtil() {} | ||||
|  | ||||
| } | ||||
| @@ -157,5 +157,9 @@ public class ChatConfig { | ||||
|                     .thenText("] "); | ||||
|         } | ||||
|  | ||||
|         private PandaTheme() {} | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private ChatConfig() {} | ||||
| } | ||||
|   | ||||
| @@ -264,7 +264,7 @@ public abstract class ChatStatic { | ||||
|      * @return a new {@link FormatableChat} with the provided translation key and parameters. | ||||
|      */ | ||||
|     public static FormatableChat translation(String key, Object... with) { | ||||
|         return new FormatableChat(Component.translatable().key(key).args(Chat.filterObjToComponentLike(with))); | ||||
|         return new FormatableChat(Component.translatable().key(key).arguments(Chat.filterObjToTranslationArgumentLike(with))); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -632,52 +632,39 @@ public abstract class ChatStatic { | ||||
|  | ||||
|  | ||||
|     private static ComponentBuilder<?, ?> componentToBuilder(Component c) { | ||||
|         ComponentBuilder<?, ?> builder; | ||||
|         if (c instanceof TextComponent) { | ||||
|             builder = Component.text() | ||||
|                     .content(((TextComponent) c).content()); | ||||
|         } | ||||
|         else if (c instanceof TranslatableComponent) { | ||||
|             builder = Component.translatable() | ||||
|                     .key(((TranslatableComponent) c).key()) | ||||
|                     .args(((TranslatableComponent) c).args()); | ||||
|         } | ||||
|         else if (c instanceof SelectorComponent) { | ||||
|             builder = Component.selector() | ||||
|                     .pattern(((SelectorComponent) c).pattern()); | ||||
|         } | ||||
|         else if (c instanceof ScoreComponent) { | ||||
|             builder = Component.score() | ||||
|                     .name(((ScoreComponent) c).name()) | ||||
|                     .objective(((ScoreComponent) c).objective()); | ||||
|         } | ||||
|         else if (c instanceof KeybindComponent) { | ||||
|             builder = Component.keybind() | ||||
|                     .keybind(((KeybindComponent) c).keybind()); | ||||
|         } | ||||
|         else if (c instanceof BlockNBTComponent) { | ||||
|             builder = Component.blockNBT() | ||||
|                     .interpret(((BlockNBTComponent) c).interpret()) | ||||
|                     .nbtPath(((BlockNBTComponent) c).nbtPath()) | ||||
|                     .pos(((BlockNBTComponent) c).pos()); | ||||
|         } | ||||
|         else if (c instanceof EntityNBTComponent) { | ||||
|             builder = Component.entityNBT() | ||||
|                     .interpret(((EntityNBTComponent) c).interpret()) | ||||
|                     .nbtPath(((EntityNBTComponent) c).nbtPath()) | ||||
|                     .selector(((EntityNBTComponent) c).selector()); | ||||
|         } | ||||
|         else if (c instanceof StorageNBTComponent) { | ||||
|             builder = Component.storageNBT() | ||||
|                     .interpret(((StorageNBTComponent) c).interpret()) | ||||
|                     .nbtPath(((StorageNBTComponent) c).nbtPath()) | ||||
|                     .storage(((StorageNBTComponent) c).storage()); | ||||
|         } | ||||
|         else { | ||||
|             throw new IllegalArgumentException("Unknown component type " + c.getClass()); | ||||
|         } | ||||
|         ComponentBuilder<?, ?> builder = switch (c) { | ||||
|             case TextComponent textComponent -> Component.text() | ||||
|                     .content(textComponent.content()); | ||||
|             case TranslatableComponent translatableComponent -> Component.translatable() | ||||
|                     .key(translatableComponent.key()).arguments(translatableComponent.arguments()); | ||||
|             case SelectorComponent selectorComponent -> Component.selector() | ||||
|                     .pattern(selectorComponent.pattern()); | ||||
|             case ScoreComponent scoreComponent -> Component.score() | ||||
|                     .name(scoreComponent.name()) | ||||
|                     .objective(scoreComponent.objective()); | ||||
|             case KeybindComponent keybindComponent -> Component.keybind() | ||||
|                     .keybind(keybindComponent.keybind()); | ||||
|             case BlockNBTComponent blockNBTComponent -> Component.blockNBT() | ||||
|                     .interpret(blockNBTComponent.interpret()) | ||||
|                     .nbtPath(blockNBTComponent.nbtPath()) | ||||
|                     .pos(blockNBTComponent.pos()); | ||||
|             case EntityNBTComponent entityNBTComponent -> Component.entityNBT() | ||||
|                     .interpret(entityNBTComponent.interpret()) | ||||
|                     .nbtPath(entityNBTComponent.nbtPath()) | ||||
|                     .selector(entityNBTComponent.selector()); | ||||
|             case StorageNBTComponent storageNBTComponent -> Component.storageNBT() | ||||
|                     .interpret(storageNBTComponent.interpret()) | ||||
|                     .nbtPath(storageNBTComponent.nbtPath()) | ||||
|                     .storage(storageNBTComponent.storage()); | ||||
|             case null, default -> throw new IllegalArgumentException("Unknown component type " + (c == null ? "null" : c.getClass())); | ||||
|         }; | ||||
|         return builder.style(c.style()).append(c.children()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a new {@link ChatStatic} instance. | ||||
|      */ | ||||
|     protected ChatStatic() {} | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import net.kyori.adventure.text.Component; | ||||
| import net.kyori.adventure.text.ComponentLike; | ||||
| import net.kyori.adventure.text.TextComponent; | ||||
| import net.kyori.adventure.text.TranslatableComponent; | ||||
| import net.kyori.adventure.text.TranslationArgument; | ||||
| import net.kyori.adventure.text.format.NamedTextColor; | ||||
| import net.kyori.adventure.text.format.TextColor; | ||||
| import net.kyori.adventure.text.format.TextDecoration; | ||||
| @@ -265,8 +266,8 @@ public class ChatUtil { | ||||
|             count += strWidth(((TextComponent)component).content(), console, actuallyBold); | ||||
|         } | ||||
|         else if (component instanceof TranslatableComponent) { | ||||
|             for (Component c : ((TranslatableComponent)component).args()) | ||||
|                 count += componentWidth(c, console, actuallyBold); | ||||
|             for (TranslationArgument c : ((TranslatableComponent)component).arguments()) | ||||
|                 count += componentWidth(c.asComponent(), console, actuallyBold); | ||||
|         } | ||||
|  | ||||
|         for (Component c : component.children()) | ||||
| @@ -480,7 +481,7 @@ public class ChatUtil { | ||||
|                 spacedRow.thenText(space); | ||||
|             } | ||||
|             if (!row.isEmpty()) | ||||
|                 spacedRow.then(row.get(row.size() - 1)); | ||||
|                 spacedRow.then(row.getLast()); | ||||
|             spacedRows.add(spacedRow.getAdv()); | ||||
|         } | ||||
|  | ||||
| @@ -657,5 +658,6 @@ public class ChatUtil { | ||||
|         return str; | ||||
|     } | ||||
|  | ||||
|     private ChatUtil() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -25,6 +25,9 @@ public class CLIBrigadierDispatcher extends BrigadierDispatcher<CLICommandSender | ||||
| 	public static final CLICommandSender CLI_CONSOLE_COMMAND_SENDER = new CLIConsoleCommandSender(); | ||||
|  | ||||
|  | ||||
| 	private CLIBrigadierDispatcher() {} | ||||
|  | ||||
|  | ||||
| 	/** | ||||
| 	 * Executes the provided command as the console. | ||||
| 	 * @param commandWithoutSlash the command, without the eventual slash at the beginning. | ||||
|   | ||||
| @@ -11,6 +11,12 @@ import org.jetbrains.annotations.NotNull; | ||||
|  * The console command sender. | ||||
|  */ | ||||
| public class CLIConsoleCommandSender implements CLICommandSender { | ||||
|  | ||||
|     /** | ||||
|      * Creates a new console command sender. | ||||
|      */ | ||||
|     protected CLIConsoleCommandSender() {} | ||||
|  | ||||
|     public String getName() { | ||||
|         return "Console"; | ||||
|     } | ||||
|   | ||||
| @@ -36,6 +36,11 @@ import static fr.pandacube.lib.chat.ChatStatic.text; | ||||
|  */ | ||||
| public class CommandAdmin extends CLIBrigadierCommand { | ||||
|  | ||||
| 	/** | ||||
| 	 * Initializes the admin command. | ||||
| 	 */ | ||||
| 	public CommandAdmin() {} | ||||
|  | ||||
| 	@Override | ||||
| 	protected LiteralArgumentBuilder<CLICommandSender> buildCommand() { | ||||
| 		return literal("admin") | ||||
|   | ||||
| @@ -9,6 +9,11 @@ import fr.pandacube.lib.cli.CLIApplication; | ||||
|  */ | ||||
| public class CommandStop extends CLIBrigadierCommand { | ||||
|  | ||||
| 	/** | ||||
| 	 * Initializes the admin command. | ||||
| 	 */ | ||||
| 	public CommandStop() {} | ||||
|  | ||||
| 	@Override | ||||
| 	protected LiteralArgumentBuilder<CLICommandSender> buildCommand() { | ||||
| 		return literal("stop") | ||||
|   | ||||
| @@ -97,5 +97,7 @@ public class CLILogger { | ||||
| 		t.start(); | ||||
| 		return ps; | ||||
| 	} | ||||
|  | ||||
| 	private CLILogger() {} | ||||
| 	 | ||||
| } | ||||
|   | ||||
| @@ -25,6 +25,11 @@ import java.util.function.Predicate; | ||||
|  */ | ||||
| public abstract class BrigadierCommand<S> { | ||||
|  | ||||
|     /** | ||||
|      * Creates a Brigadier command. | ||||
|      */ | ||||
|     public BrigadierCommand() {} | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Returns a builder for this command. | ||||
|   | ||||
| @@ -21,6 +21,11 @@ public abstract class BrigadierDispatcher<S> { | ||||
|  | ||||
|     private final CommandDispatcher<S> dispatcher = new CommandDispatcher<>(); | ||||
|  | ||||
|     /** | ||||
|      * Creates a new Dispatcher instance. | ||||
|      */ | ||||
|     public BrigadierDispatcher() {} | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Registers the provided command node into this dispatcher. | ||||
|   | ||||
| @@ -140,4 +140,8 @@ public class BrigadierSuggestionsUtil { | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|     private BrigadierSuggestionsUtil() {} | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -70,6 +70,11 @@ public abstract class BackupCleaner implements UnaryOperator<TreeSet<LocalDateTi | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Create a backup cleaner. | ||||
|      */ | ||||
|     public BackupCleaner() {} | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Creates a new {@link BackupCleaner} that keeps the archives kept by this {@link BackupCleaner} or by the provided | ||||
|   | ||||
| @@ -54,7 +54,7 @@ public class CronScheduler { | ||||
|                 long now = System.currentTimeMillis(); | ||||
|  | ||||
|                 if (!tasks.isEmpty()) { | ||||
|                     CronTask next = tasks.get(0); | ||||
|                     CronTask next = tasks.getFirst(); | ||||
|                     if (next.nextRun <= now) { | ||||
|                         next.runAsync(); | ||||
|                         setLastRun(next.taskId, now); | ||||
| @@ -224,5 +224,6 @@ public class CronScheduler { | ||||
|                 .toEpochMilli(); | ||||
|     } | ||||
|  | ||||
|     private CronScheduler() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -163,4 +163,6 @@ public class Json { | ||||
| 		} | ||||
| 	}*/ | ||||
|  | ||||
| 	private Json() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -30,6 +30,8 @@ public class ThrowableAdapter implements JsonSerializer<Throwable>, JsonDeserial | ||||
|  | ||||
|     /* package */ static final TypeAdapterFactory FACTORY = TreeTypeAdapter.newTypeHierarchyFactory(Throwable.class, new ThrowableAdapter()); | ||||
|  | ||||
|     private ThrowableAdapter() {} | ||||
|  | ||||
|  | ||||
|     @Override | ||||
|     public Throwable deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | ||||
|   | ||||
| @@ -237,5 +237,8 @@ public class TypeConverter { | ||||
| 		} | ||||
| 		 | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	private TypeConverter() {} | ||||
| 	 | ||||
| } | ||||
|   | ||||
| @@ -58,6 +58,9 @@ public record MinecraftVersionList( | ||||
|  | ||||
|         private static final TypeToken<Map<String, Integer>> MAP_STR_INT_TYPE = new TypeToken<>() { }; | ||||
|         private static final TypeToken<Map<Integer, List<String>>> MAP_INT_LIST_STRING_TYPE = new TypeToken<>() { }; | ||||
|  | ||||
|         private MinecraftVersionListAdapter() {} | ||||
|  | ||||
|         @Override | ||||
|         public MinecraftVersionList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | ||||
|             if (!(json instanceof JsonObject jsonObj)) | ||||
|   | ||||
| @@ -135,4 +135,7 @@ public class MinecraftVersionUtil { | ||||
|         return set; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private MinecraftVersionUtil() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -56,18 +56,21 @@ | ||||
|                                     <artifact>org.apache.commons:commons-dbcp2</artifact> | ||||
|                                     <excludes> | ||||
|                                         <exclude>META-INF/MANIFEST.MF</exclude> | ||||
|                                         <exclude>META-INF/versions/9/**</exclude> | ||||
|                                     </excludes> | ||||
|                                 </filter> | ||||
|                                 <filter> | ||||
|                                     <artifact>org.apache.commons:commons-pool2</artifact> | ||||
|                                     <excludes> | ||||
|                                         <exclude>META-INF/MANIFEST.MF</exclude> | ||||
|                                         <exclude>META-INF/versions/9/**</exclude> | ||||
|                                     </excludes> | ||||
|                                 </filter> | ||||
|                                 <filter> | ||||
|                                     <artifact>commons-logging:commons-logging</artifact> | ||||
|                                     <excludes> | ||||
|                                         <exclude>META-INF/MANIFEST.MF</exclude> | ||||
|                                         <exclude>META-INF/versions/9/**</exclude> | ||||
|                                     </excludes> | ||||
|                                 </filter> | ||||
|                             </filters> | ||||
|   | ||||
| @@ -16,6 +16,11 @@ import java.util.stream.Collectors; | ||||
|  */ | ||||
| public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> { | ||||
|  | ||||
|     /** | ||||
|      * Creates an empty list of sql elements. | ||||
|      */ | ||||
|     public SQLElementList() {} | ||||
|  | ||||
|     /** | ||||
|      * Stores all the values modified by {@link #setCommon(SQLField, Object)}. | ||||
|      */ | ||||
|   | ||||
| @@ -232,12 +232,8 @@ public class SQLField<E extends SQLElement<E>, T> { | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     @SuppressWarnings({"rawtypes", "unchecked"}) | ||||
|     public Object fromJavaTypeToJDBCType(Object value) throws DBException { | ||||
|     /* package */ Object fromJavaTypeToJDBCType(Object value) throws DBException { | ||||
|         Object ret = value; | ||||
|         if (value != null && type instanceof SQLCustomType customType) { | ||||
|             try { | ||||
| @@ -250,7 +246,7 @@ public class SQLField<E extends SQLElement<E>, T> { | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     public Collection<Object> fromListJavaTypeToJDBCType(Collection<?> values) throws DBException { | ||||
|     /* package */ Collection<Object> fromListJavaTypeToJDBCType(Collection<?> values) throws DBException { | ||||
|         if (values == null) | ||||
|             return null; | ||||
|         List<Object> ret = new ArrayList<>(values.size()); | ||||
|   | ||||
| @@ -13,6 +13,11 @@ import fr.pandacube.lib.util.log.Log; | ||||
|  */ | ||||
| public abstract class SQLWhere<E extends SQLElement<E>> { | ||||
|  | ||||
|     /** | ||||
|      * Creates a SQL WHERE expression. | ||||
|      */ | ||||
|     protected SQLWhere() {} | ||||
|  | ||||
|     /* package */ abstract ParameterizedSQLString toSQL() throws DBException; | ||||
|  | ||||
|     @Override | ||||
| @@ -69,6 +74,7 @@ public abstract class SQLWhere<E extends SQLElement<E>> { | ||||
|      * Create a custom SQL {@code WHERE} expression. | ||||
|      * @param whereExpr the raw SQL {@code WHERE} expression. | ||||
|      * @return a new SQL {@code WHERE} expression. | ||||
|      * @param <E> the table type. | ||||
|      */ | ||||
|     public static <E extends SQLElement<E>> SQLWhere<E> expression(String whereExpr) { | ||||
|         return expression(whereExpr, List.of()); | ||||
| @@ -79,6 +85,7 @@ public abstract class SQLWhere<E extends SQLElement<E>> { | ||||
|      * @param whereExpr the raw SQL {@code WHERE} expression. | ||||
|      * @param params the parameters of the provided expression. | ||||
|      * @return a new SQL {@code WHERE} expression. | ||||
|      * @param <E> the table type. | ||||
|      */ | ||||
|     public static <E extends SQLElement<E>> SQLWhere<E> expression(String whereExpr, List<Object> params) { | ||||
|         return new SQLWhereCustomExpression<>(whereExpr, params); | ||||
| @@ -89,6 +96,7 @@ public abstract class SQLWhere<E extends SQLElement<E>> { | ||||
|      * @param leftExpr the raw SQL left operand. | ||||
|      * @param valuesIn the values on the right of the {@code IN} operator. | ||||
|      * @return a new SQL {@code WHERE} expression. | ||||
|      * @param <E> the table type. | ||||
|      */ | ||||
|     public static <E extends SQLElement<E>> SQLWhere<E> expressionIn(String leftExpr, Collection<?> valuesIn) { | ||||
|         return expressionIn(leftExpr, List.of(), valuesIn); | ||||
| @@ -100,6 +108,7 @@ public abstract class SQLWhere<E extends SQLElement<E>> { | ||||
|      * @param leftParams the parameters of the left operand. | ||||
|      * @param valuesIn the values on the right of the {@code IN} operator. | ||||
|      * @return a new SQL {@code WHERE} expression. | ||||
|      * @param <E> the table type. | ||||
|      */ | ||||
|     public static <E extends SQLElement<E>> SQLWhere<E> expressionIn(String leftExpr, List<Object> leftParams, Collection<?> valuesIn) { | ||||
|         return new SQLWhereInCustom<>(leftExpr, leftParams, valuesIn); | ||||
|   | ||||
| @@ -21,4 +21,8 @@ | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
|  | ||||
|     <properties> | ||||
|         <maven.javadoc.skip>true</maven.javadoc.skip> | ||||
|     </properties> | ||||
|  | ||||
| </project> | ||||
| @@ -82,6 +82,12 @@ public class PandalibPaperPermissions implements Listener { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	/** | ||||
| 	 * Creates a {@link PandalibPaperPermissions} instance. | ||||
| 	 */ | ||||
| 	private PandalibPaperPermissions() {} | ||||
|  | ||||
| 	/** | ||||
| 	 * Player login event handler. | ||||
| 	 * @param event the event. | ||||
|   | ||||
| @@ -2,38 +2,26 @@ package fr.pandacube.lib.paper.commands; | ||||
|  | ||||
| import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; | ||||
| import com.mojang.brigadier.CommandDispatcher; | ||||
| import com.mojang.brigadier.arguments.ArgumentType; | ||||
| import com.mojang.brigadier.context.CommandContext; | ||||
| import com.mojang.brigadier.exceptions.CommandSyntaxException; | ||||
| import com.mojang.brigadier.suggestion.SuggestionProvider; | ||||
| import com.mojang.brigadier.tree.CommandNode; | ||||
| import com.mojang.brigadier.tree.LiteralCommandNode; | ||||
| import com.mojang.brigadier.tree.RootCommandNode; | ||||
| import fr.pandacube.lib.chat.Chat; | ||||
| import fr.pandacube.lib.commands.BadCommandUsage; | ||||
| import fr.pandacube.lib.commands.BrigadierCommand; | ||||
| import fr.pandacube.lib.commands.SuggestionsSupplier; | ||||
| import fr.pandacube.lib.paper.permissions.PandalibPaperPermissions; | ||||
| import fr.pandacube.lib.paper.reflect.PandalibPaperReflect; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftVector; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.VanillaCommandWrapper; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.BlockPosArgument; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Commands; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.ComponentArgument; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Coordinates; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.EntityArgument; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.EntitySelector; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Vec3Argument; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.core.BlockPos; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ServerPlayer; | ||||
| import fr.pandacube.lib.paper.reflect.wrapper.paper.PaperAdventure; | ||||
| import fr.pandacube.lib.players.standalone.AbstractOffPlayer; | ||||
| import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer; | ||||
| import fr.pandacube.lib.players.standalone.AbstractPlayerManager; | ||||
| import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; | ||||
| import fr.pandacube.lib.util.log.Log; | ||||
| import net.kyori.adventure.text.Component; | ||||
| import io.papermc.paper.command.brigadier.CommandSourceStack; | ||||
| import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; | ||||
| import org.bukkit.Bukkit; | ||||
| import org.bukkit.World; | ||||
| import org.bukkit.command.Command; | ||||
| @@ -41,18 +29,10 @@ import org.bukkit.command.CommandMap; | ||||
| import org.bukkit.command.CommandSender; | ||||
| import org.bukkit.command.ConsoleCommandSender; | ||||
| import org.bukkit.command.PluginCommand; | ||||
| import org.bukkit.command.defaults.BukkitCommand; | ||||
| import org.bukkit.entity.Entity; | ||||
| import org.bukkit.entity.Player; | ||||
| import org.bukkit.event.EventHandler; | ||||
| import org.bukkit.event.Listener; | ||||
| import org.bukkit.event.player.PlayerCommandSendEvent; | ||||
| import org.bukkit.event.server.ServerLoadEvent; | ||||
| import org.bukkit.plugin.Plugin; | ||||
| import org.bukkit.util.BlockVector; | ||||
| import org.bukkit.util.Vector; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| @@ -64,7 +44,9 @@ import static fr.pandacube.lib.util.ThrowableUtil.wrapEx; | ||||
| /** | ||||
|  * Abstract class to hold a command to be integrated into a Paper server vanilla command dispatcher. | ||||
|  */ | ||||
| public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBrigadierCommandSource> implements Listener { | ||||
| @SuppressWarnings("UnstableApiUsage") | ||||
| public abstract class PaperBrigadierCommand extends BrigadierCommand<CommandSourceStack> implements Listener { | ||||
|  | ||||
|  | ||||
|     private static final Commands vanillaCommandDispatcher; | ||||
|     private static final CommandDispatcher<BukkitBrigadierCommandSource> nmsDispatcher; | ||||
| @@ -91,6 +73,7 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|                 return; | ||||
|             } | ||||
|             Log.info("Removing Bukkit command /" + name + " (" + getCommandIdentity(bukkitCommand) + ")"); | ||||
|             Log.warning("[1.20.6 update] Please test that the bukkit command removal is actually working."); | ||||
|             bukkitCmdMap.getKnownCommands().remove(name.toLowerCase(java.util.Locale.ENGLISH)); | ||||
|             bukkitCommand.unregister(bukkitCmdMap); | ||||
|  | ||||
| @@ -131,7 +114,13 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|     /** | ||||
|      * The command node of this command. | ||||
|      */ | ||||
|     protected final LiteralCommandNode<BukkitBrigadierCommandSource> commandNode; | ||||
|     protected final LiteralCommandNode<CommandSourceStack> commandNode; | ||||
|     /** | ||||
|      * The command requested aliases. | ||||
|      */ | ||||
|     protected final String[] aliases; | ||||
|  | ||||
|     protected final String description; | ||||
|  | ||||
|     private final RegistrationPolicy registrationPolicy; | ||||
|  | ||||
| @@ -147,12 +136,14 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|         plugin = pl; | ||||
|         registrationPolicy = regPolicy; | ||||
|         commandNode = buildCommand().build(); | ||||
|         String[] aliasesTmp = getAliases(); | ||||
|         aliases = aliasesTmp == null ? new String[0] : aliasesTmp; | ||||
|         description = getDescription(); | ||||
|         postBuildCommand(commandNode); | ||||
|         register(); | ||||
|         Bukkit.getPluginManager().registerEvents(this, plugin); | ||||
|         try { | ||||
|             PandalibPaperPermissions.addPermissionMapping("minecraft.command." + commandNode.getLiteral().toLowerCase(), getTargetPermission().toLowerCase()); | ||||
|         } catch (NoClassDefFoundError ignored) { } | ||||
|         //try { | ||||
|         //    PandalibPaperPermissions.addPermissionMapping("minecraft.command." + commandNode.getLiteral().toLowerCase(), getTargetPermission().toLowerCase()); | ||||
|         //} catch (NoClassDefFoundError ignored) { } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -167,130 +158,28 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|  | ||||
|  | ||||
|     private void register() { | ||||
|         plugin.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> { | ||||
|  | ||||
|         String[] aliases = getAliases(); | ||||
|         if (aliases == null) | ||||
|             aliases = new String[0]; | ||||
|             registeredAliases = new HashSet<>(event.registrar().register(commandNode, description, List.of(aliases))); | ||||
|  | ||||
|         String pluginName = plugin.getName().toLowerCase(); | ||||
|             if (registrationPolicy == RegistrationPolicy.ALL) { | ||||
|                 // enforce registration of aliases | ||||
|                 for (String alias : aliases) { | ||||
|                     if (!registeredAliases.contains(alias)) | ||||
|                         registeredAliases.addAll(event.registrar().register(getAliasNode(alias), description)); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         registeredAliases = new HashSet<>(); | ||||
|         registerNode(commandNode, false); | ||||
|         registerAlias(pluginName + ":" + commandNode.getLiteral(), true); | ||||
|  | ||||
|         for (String alias : aliases) { | ||||
|             registerAlias(alias, false); | ||||
|             registerAlias(pluginName + ":" + alias, true); | ||||
|         } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private void registerAlias(String alias, boolean prefixed) { | ||||
|         LiteralCommandNode<BukkitBrigadierCommandSource> node = literal(alias) | ||||
|     private LiteralCommandNode<CommandSourceStack> getAliasNode(String alias) { | ||||
|          return literal(alias) | ||||
|                 .requires(commandNode.getRequirement()) | ||||
|                 .executes(commandNode.getCommand()) | ||||
|                 .redirect(commandNode) | ||||
|                 .build(); | ||||
|         registerNode(node, prefixed); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private void registerNode(LiteralCommandNode<BukkitBrigadierCommandSource> node, boolean prefixed) { | ||||
|         RootCommandNode<BukkitBrigadierCommandSource> root = getRootNode(); | ||||
|         String name = node.getLiteral(); | ||||
|         boolean isAlias = node.getRedirect() == commandNode; | ||||
|         boolean forceRegistration = switch (registrationPolicy) { | ||||
|             case NONE -> false; | ||||
|             case ONLY_BASE_COMMAND -> prefixed || !isAlias; | ||||
|             case ALL -> true; | ||||
|         }; | ||||
|  | ||||
|         // nmsDispatcher integration and conflit resolution | ||||
|         boolean nmsRegister = false, nmsRegistered = false; | ||||
|         CommandNode<BukkitBrigadierCommandSource> nmsConflicted = root.getChild(name); | ||||
|         if (nmsConflicted != null) { | ||||
|  | ||||
|             if (isFromThisCommand(nmsConflicted)) { | ||||
|                 // this command is already registered in NMS. Don’t need to register again | ||||
|                 nmsRegistered = true; | ||||
|             } | ||||
|             else if (forceRegistration) { | ||||
|                 nmsRegister = true; | ||||
|                 Log.info("Overwriting Brigadier command /" + name); | ||||
|             } | ||||
|             else if (prefixed || !isAlias) { | ||||
|                 Log.severe("/" + name + " already in NMS Brigadier instance." | ||||
|                         + " Wont replace it because registration is not forced for prefixed or initial name of a command."); | ||||
|             } | ||||
|             else { // conflict, won't replace, not forced but only an alias anyway | ||||
|                 Log.info("/" + name + " already in NMS Brigadier instance." | ||||
|                         + " Wont replace it because registration is not forced for a non-prefixed alias."); | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             nmsRegister = true; | ||||
|         } | ||||
|  | ||||
|         if (nmsRegister) { | ||||
|             @SuppressWarnings("unchecked") | ||||
|             var rCommandNode = ReflectWrapper.wrapTyped(root, fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode.class); | ||||
|             rCommandNode.removeCommand(name); | ||||
|             root.addChild(node); | ||||
|             nmsRegistered = true; | ||||
|         } | ||||
|  | ||||
|         if (!nmsRegistered) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         registeredAliases.add(name); | ||||
|  | ||||
|         // bukkit dispatcher conflict resolution | ||||
|         boolean bukkitRegister = false; | ||||
|         CommandMap bukkitCmdMap = Bukkit.getCommandMap(); | ||||
|         Command bukkitConflicted = bukkitCmdMap.getCommand(name); | ||||
|         if (bukkitConflicted != null) { | ||||
|             if (!isFromThisCommand(bukkitConflicted)) { | ||||
|                 if (forceRegistration) { | ||||
|                     bukkitRegister = true; | ||||
|                     Log.info("Overwriting Bukkit command /" + name | ||||
|                             + " (" + getCommandIdentity(bukkitConflicted) + ")"); | ||||
|                 } | ||||
|                 else if (prefixed || !isAlias) { | ||||
|                     Log.severe("/" + name + " already in Bukkit dispatcher (" + getCommandIdentity(bukkitConflicted) + ")." + | ||||
|                             " Wont replace it because registration is not forced for prefixed or initial name of a command."); | ||||
|                 } | ||||
|                 else { | ||||
|                     Log.info("/" + name + " already in Bukkit dispatcher (" + getCommandIdentity(bukkitConflicted) + ")." + | ||||
|                             " Wont replace it because registration is not forced for a non-prefixed alias."); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             bukkitRegister = true; | ||||
|         } | ||||
|  | ||||
|         if (bukkitRegister) { | ||||
|             bukkitCmdMap.getKnownCommands().remove(name.toLowerCase()); | ||||
|             if (bukkitConflicted != null) | ||||
|                 bukkitConflicted.unregister(bukkitCmdMap); | ||||
|  | ||||
|             Command newCommand = new VanillaCommandWrapper(vanillaCommandDispatcher, node).__getRuntimeInstance(); | ||||
|             bukkitCmdMap.getKnownCommands().put(name.toLowerCase(), newCommand); | ||||
|             newCommand.register(bukkitCmdMap); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private boolean isFromThisCommand(CommandNode<BukkitBrigadierCommandSource> node) { | ||||
|         return node == commandNode || node.getRedirect() == commandNode; | ||||
|     } | ||||
|  | ||||
|     private boolean isFromThisCommand(Command bukkitCmd) { | ||||
|         if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCmd)) { | ||||
|             return isFromThisCommand(ReflectWrapper.wrapTyped((BukkitCommand) bukkitCmd, VanillaCommandWrapper.class).vanillaCommand()); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     private static String getCommandIdentity(Command bukkitCmd) { | ||||
| @@ -305,25 +194,6 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Player command sender event handler. | ||||
|      * @param event the event. | ||||
|      */ | ||||
|     @EventHandler | ||||
|     public void onPlayerCommandSend(PlayerCommandSendEvent event) { | ||||
|         event.getCommands().removeAll(registeredAliases.stream().map(s -> "minecraft:" + s).toList()); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Server load event handler. | ||||
|      * @param event the event. | ||||
|      */ | ||||
|     @EventHandler | ||||
|     public void onServerLoad(ServerLoadEvent event) { | ||||
|         register(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -342,6 +212,15 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|      */ | ||||
|     protected abstract String getTargetPermission(); | ||||
|  | ||||
|     /** | ||||
|      * Returns the permission that should be tested instead of "minecraft.command.cmdName". The conversion from the | ||||
|      * minecraft prefixed permission node to the returned node is done by the {@code pandalib-paper-permissions} if it | ||||
|      * is present in the classpath during runtime. | ||||
|      * @return the permission that should be tested instead of "minecraft.command.cmdName". | ||||
|      */ | ||||
|     protected String getDescription() { | ||||
|         return "A command from " + plugin.getName(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
| @@ -355,13 +234,14 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|  | ||||
|  | ||||
|  | ||||
|     public boolean isConsole(BukkitBrigadierCommandSource wrapper) { | ||||
|  | ||||
|     public boolean isConsole(CommandSourceStack wrapper) { | ||||
|         return isConsole(getCommandSender(wrapper)); | ||||
|     } | ||||
|     public boolean isPlayer(BukkitBrigadierCommandSource wrapper) { | ||||
|     public boolean isPlayer(CommandSourceStack wrapper) { | ||||
|         return isPlayer(getCommandSender(wrapper)); | ||||
|     } | ||||
|     public Predicate<BukkitBrigadierCommandSource> hasPermission(String permission) { | ||||
|     public Predicate<CommandSourceStack> hasPermission(String permission) { | ||||
|         return wrapper -> getCommandSender(wrapper).hasPermission(permission); | ||||
|     } | ||||
|  | ||||
| @@ -395,7 +275,7 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|      * @param context the command context from which to get the Bukkit command sender. | ||||
|      * @return the Bukkit command sender. | ||||
|      */ | ||||
|     public static CommandSender getCommandSender(CommandContext<BukkitBrigadierCommandSource> context) { | ||||
|     public static CommandSender getCommandSender(CommandContext<CommandSourceStack> context) { | ||||
|         return getCommandSender(context.getSource()); | ||||
|     } | ||||
|  | ||||
| @@ -404,8 +284,8 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|      * @param wrapper the wrapper from which to get the Bukkit command sender. | ||||
|      * @return the Bukkit command sender. | ||||
|      */ | ||||
|     public static CommandSender getCommandSender(BukkitBrigadierCommandSource wrapper) { | ||||
|         return wrapper.getBukkitSender(); | ||||
|     public static CommandSender getCommandSender(CommandSourceStack wrapper) { | ||||
|         return wrapper.getSender(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -413,8 +293,8 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|      * @param sender the command sender. | ||||
|      * @return a new instance of a command sender wrapper for the provided command sender. | ||||
|      */ | ||||
|     public static BukkitBrigadierCommandSource getBrigadierCommandSource(CommandSender sender) { | ||||
|         return VanillaCommandWrapper.getListener(sender); | ||||
|     public static CommandSourceStack getBrigadierCommandSource(CommandSender sender) { | ||||
|         throw new UnsupportedOperationException("The 1.20.6 Paper API update uses a different wrapper for Brigadier command sender."); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @@ -443,7 +323,7 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|      * @param suggestions the suggestions to wrap. | ||||
|      * @return a {@link SuggestionProvider} generating the suggestions from the provided {@link SuggestionsSupplier}. | ||||
|      */ | ||||
|     public SuggestionProvider<BukkitBrigadierCommandSource> wrapSuggestions(SuggestionsSupplier<CommandSender> suggestions) { | ||||
|     public SuggestionProvider<CommandSourceStack> wrapSuggestions(SuggestionsSupplier<CommandSender> suggestions) { | ||||
|         return wrapSuggestions(suggestions, PaperBrigadierCommand::getCommandSender); | ||||
|     } | ||||
|  | ||||
| @@ -456,7 +336,7 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|      * @param cmd the command executor to wrap. | ||||
|      * @return a wrapper command executor. | ||||
|      */ | ||||
|     protected static com.mojang.brigadier.Command<BukkitBrigadierCommandSource> wrapCommand(com.mojang.brigadier.Command<BukkitBrigadierCommandSource> cmd) { | ||||
|     protected static com.mojang.brigadier.Command<CommandSourceStack> wrapCommand(com.mojang.brigadier.Command<CommandSourceStack> cmd) { | ||||
|         return context -> { | ||||
|             try { | ||||
|                 return cmd.run(context); | ||||
| @@ -485,181 +365,13 @@ public abstract class PaperBrigadierCommand extends BrigadierCommand<BukkitBriga | ||||
|      */ | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Creates a new instance of the Brigadier argument type {@code minecraft:entity}. | ||||
|      * @param singleTarget if this argument takes only a single target. | ||||
|      * @param playersOnly if this argument takes players only. | ||||
|      * @return the {@code minecraft:entity} argument type with the specified parameters. | ||||
|      */ | ||||
|     public static ArgumentType<Object> argumentMinecraftEntity(boolean singleTarget, boolean playersOnly) { | ||||
|         if (playersOnly) { | ||||
|             return singleTarget ? EntityArgument.player() : EntityArgument.players(); | ||||
|         } | ||||
|         else { | ||||
|             return singleTarget ? EntityArgument.entity() : EntityArgument.entities(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the value of the provided argument of type {@code minecraft:entity} (list of entities), from the provided context. | ||||
|      * @param context the command execution context. | ||||
|      * @param argument the argument name. | ||||
|      * @return the value of the argument, or null if not found. | ||||
|      */ | ||||
|     public List<Entity> tryGetMinecraftEntityArgument(CommandContext<BukkitBrigadierCommandSource> context, String argument) { | ||||
|         EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); | ||||
|         if (es == null) | ||||
|             return null; | ||||
|         List<fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity> nmsEntityList = es.findEntities(context.getSource()); | ||||
|         List<Entity> entityList = new ArrayList<>(nmsEntityList.size()); | ||||
|         for (fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity nmsEntity : nmsEntityList) { | ||||
|             entityList.add(nmsEntity.getBukkitEntity()); | ||||
|         } | ||||
|         return entityList; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the value of the provided argument of type {@code minecraft:entity} (list of players), from the provided context. | ||||
|      * @param context the command execution context. | ||||
|      * @param argument the argument name. | ||||
|      * @return the value of the argument, or null if not found. | ||||
|      */ | ||||
|     public List<Player> tryGetMinecraftEntityArgumentPlayers(CommandContext<BukkitBrigadierCommandSource> context, String argument) { | ||||
|         EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); | ||||
|         if (es == null) | ||||
|             return null; | ||||
|         List<ServerPlayer> nmsPlayerList = es.findPlayers(context.getSource()); | ||||
|         List<Player> playerList = new ArrayList<>(nmsPlayerList.size()); | ||||
|         for (ServerPlayer nmsPlayer : nmsPlayerList) { | ||||
|             playerList.add(nmsPlayer.getBukkitEntity()); | ||||
|         } | ||||
|         return playerList; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the value of the provided argument of type {@code minecraft:entity} (one entity), from the provided context. | ||||
|      * @param context the command execution context. | ||||
|      * @param argument the argument name. | ||||
|      * @return the value of the argument, or null if not found. | ||||
|      */ | ||||
|     public Entity tryGetMinecraftEntityArgumentOneEntity(CommandContext<BukkitBrigadierCommandSource> context, String argument) { | ||||
|         EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); | ||||
|         if (es == null) | ||||
|             return null; | ||||
|         fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity nmsEntity = es.findSingleEntity(context.getSource()); | ||||
|         return nmsEntity == null ? null : nmsEntity.getBukkitEntity(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the value of the provided argument of type {@code minecraft:entity} (one player), from the provided context. | ||||
|      * @param context the command execution context. | ||||
|      * @param argument the argument name. | ||||
|      * @return the value of the argument, or null if not found. | ||||
|      */ | ||||
|     public Player tryGetMinecraftEntityArgumentOnePlayer(CommandContext<BukkitBrigadierCommandSource> context, String argument) { | ||||
|         EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); | ||||
|         if (es == null) | ||||
|             return null; | ||||
|         ServerPlayer nmsPlayer = es.findSinglePlayer(context.getSource()); | ||||
|         return nmsPlayer == null ? null : nmsPlayer.getBukkitEntity(); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Creates a new instance of the Brigadier argument type {@code minecraft:block_pos}. | ||||
|      * @return the {@code minecraft:block_pos} argument type. | ||||
|      */ | ||||
|     public static ArgumentType<Object> argumentMinecraftBlockPosition() { | ||||
|         return BlockPosArgument.blockPos(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the value of the provided argument of type {@code minecraft:block_pos}, from the provided context. | ||||
|      * @param context the command execution context. | ||||
|      * @param argument the argument name. | ||||
|      * @param deflt a default value if the argument is not found. | ||||
|      * @return the value of the argument. | ||||
|      */ | ||||
|     public BlockVector tryGetMinecraftBlockPositionArgument(CommandContext<BukkitBrigadierCommandSource> context, | ||||
|                                                             String argument, BlockVector deflt) { | ||||
|         return tryGetArgument(context, argument, Coordinates.MAPPING.runtimeClass(), nmsCoordinate -> { | ||||
|             BlockPos bp = ReflectWrapper.wrap(nmsCoordinate, Coordinates.class).getBlockPos(context.getSource()); | ||||
|             return new BlockVector(bp.getX(), bp.getY(), bp.getZ()); | ||||
|         }, deflt); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Creates a new instance of the Brigadier argument type {@code minecraft:vec3}. | ||||
|      * @return the {@code minecraft:vec3} argument type. | ||||
|      */ | ||||
|     public static ArgumentType<Object> argumentMinecraftVec3() { | ||||
|         return Vec3Argument.vec3(true); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the value of the provided argument of type {@code minecraft:vec3}, from the provided context. | ||||
|      * @param context the command execution context. | ||||
|      * @param argument the argument name. | ||||
|      * @param deflt a default value if the argument is not found. | ||||
|      * @return the value of the argument. | ||||
|      */ | ||||
|     public Vector tryGetMinecraftVec3Argument(CommandContext<BukkitBrigadierCommandSource> context, String argument, | ||||
|                                               Vector deflt) { | ||||
|         return tryGetArgument(context, argument, Coordinates.MAPPING.runtimeClass(), | ||||
|                 nmsCoordinate -> CraftVector.toBukkit( | ||||
|                         ReflectWrapper.wrap(nmsCoordinate, Coordinates.class).getPosition(context.getSource()) | ||||
|                 ), | ||||
|                 deflt); | ||||
|     } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Creates a new instance of the Brigadier argument type {@code minecraft:component}. | ||||
|      * @return the {@code minecraft:component} argument type. | ||||
|      */ | ||||
|     public static ArgumentType<Object> argumentMinecraftChatComponent() { | ||||
|         return ComponentArgument.textComponent(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the value of the provided argument of type {@code minecraft:component}, from the provided context. | ||||
|      * @param context the command execution context. | ||||
|      * @param argument the argument name. | ||||
|      * @param deflt a default value if the argument is not found. | ||||
|      * @return the value of the argument. | ||||
|      */ | ||||
|     public Component tryGetMinecraftChatComponentArgument(CommandContext<BukkitBrigadierCommandSource> context, | ||||
|                                                           String argument, Component deflt) { | ||||
|         return tryGetArgument(context, argument, | ||||
|                 fr.pandacube.lib.paper.reflect.wrapper.minecraft.network.chat.Component.MAPPING.runtimeClass(), | ||||
|                 nmsComp -> PaperAdventure.asAdventure( | ||||
|                         ReflectWrapper.wrap(nmsComp, | ||||
|                                 fr.pandacube.lib.paper.reflect.wrapper.minecraft.network.chat.Component.class) | ||||
|                 ), | ||||
|                 deflt); | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * All possible choices on how to force the registration of a command, based on certain conditions. | ||||
|      */ | ||||
|     public enum RegistrationPolicy { | ||||
|         /** | ||||
|          * Do not force to register a command node or an alias if there is already a command with that name in the | ||||
|          * vanilla Brigadier dispatcher. | ||||
|          * Note that all plugin-name-prefixed aliases will be registered anyway. | ||||
|          */ | ||||
|         NONE, | ||||
|         /** | ||||
|          * Force only the base command (but not the aliases) to be registered, even if a command with that name already | ||||
|          * exists in the vanilla Brigadier dispatcher. | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| package fr.pandacube.lib.paper.util; | ||||
|  | ||||
| import com.destroystokyo.paper.Namespaced; | ||||
| import com.google.common.collect.Streams; | ||||
| import fr.pandacube.lib.chat.Chat; | ||||
| import net.kyori.adventure.text.Component; | ||||
| @@ -13,10 +12,8 @@ import org.bukkit.inventory.meta.Damageable; | ||||
| import org.bukkit.inventory.meta.ItemMeta; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| import java.util.function.Consumer; | ||||
|  | ||||
| import static fr.pandacube.lib.chat.ChatStatic.chatComponent; | ||||
| @@ -183,7 +180,7 @@ public class ItemStackBuilder { | ||||
|  | ||||
| 	public ItemStackBuilder fakeEnchant(boolean apply) { | ||||
| 		if (apply) { | ||||
| 			enchant(Enchantment.DURABILITY, 1); | ||||
| 			enchant(Enchantment.UNBREAKING, 1); | ||||
| 			return hideEnchants(); | ||||
| 		} | ||||
| 		return this; | ||||
| @@ -196,22 +193,6 @@ public class ItemStackBuilder { | ||||
| 	public ItemStackBuilder unbreakable(boolean unbreakable) { | ||||
| 		return meta(m -> m.setUnbreakable(unbreakable)); | ||||
| 	} | ||||
|  | ||||
| 	public ItemStackBuilder canDestroy(Set<Material> destroyable) { | ||||
| 		return canDestroy(destroyable.stream().map(m -> (Namespaced) m.getKey()).toList()); | ||||
| 	} | ||||
|  | ||||
| 	public ItemStackBuilder canPlaceOn(Set<Material> placeOn) { | ||||
| 		return canPlaceOn(placeOn.stream().map(m -> (Namespaced) m.getKey()).toList()); | ||||
| 	} | ||||
|  | ||||
| 	public ItemStackBuilder canDestroy(Collection<Namespaced> destroyable) { | ||||
| 		return meta(m -> m.setDestroyableKeys(destroyable)); | ||||
| 	} | ||||
|  | ||||
| 	public ItemStackBuilder canPlaceOn(Collection<Namespaced> placeOn) { | ||||
| 		return meta(m -> m.setPlaceableKeys(placeOn)); | ||||
| 	} | ||||
| 	 | ||||
| 	public ItemStackBuilder damage(int d) { | ||||
| 		return meta(m -> m.setDamage(d), Damageable.class); | ||||
|   | ||||
| @@ -18,6 +18,7 @@ import org.jetbrains.annotations.Nullable; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.Map; | ||||
| import java.util.Map.Entry; | ||||
| import java.util.Objects; | ||||
| import java.util.TreeMap; | ||||
| import java.util.function.IntUnaryOperator; | ||||
| @@ -98,7 +99,7 @@ public record PlayerDataWrapper(CompoundTag data) { | ||||
|         Inventory inv = Bukkit.createInventory(null, bukkitType); | ||||
|         if (stacks.isEmpty()) | ||||
|             return inv; | ||||
|         for (Map.Entry<Integer, ItemStack> is : stacks.entrySet()) { | ||||
|         for (Entry<Integer, ItemStack> is : stacks.entrySet()) { | ||||
|             inv.setItem(nbtToBukkitSlotConverter.applyAsInt(is.getKey()), is.getValue()); | ||||
|         } | ||||
|         return inv; | ||||
| @@ -142,7 +143,7 @@ public record PlayerDataWrapper(CompoundTag data) { | ||||
|  | ||||
|     private void setRawInventoryContent(String key, Map<Integer, ItemStack> stacks) { | ||||
|         ListTag list = new ListTag(); | ||||
|         for (Map.Entry<Integer, ItemStack> is : stacks.entrySet()) { | ||||
|         for (Entry<Integer, ItemStack> is : stacks.entrySet()) { | ||||
|             ItemStack stack = filterStack(is.getValue()); | ||||
|             if (stack == null) | ||||
|                 continue; | ||||
| @@ -312,6 +313,7 @@ public record PlayerDataWrapper(CompoundTag data) { | ||||
|                 case LEGS -> Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(Material.AIR)); | ||||
|                 case CHEST -> Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(Material.AIR)); | ||||
|                 case HEAD -> Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(Material.AIR)); | ||||
|                 case BODY -> new ItemStack(Material.AIR); // for horses/wolves armor | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -119,5 +119,7 @@ public class PermissionExpressionParser { | ||||
| 			return super.evaluate(constant, evaluationContext); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private PermissionExpressionParser() {} | ||||
| 	 | ||||
| } | ||||
|   | ||||
| @@ -199,5 +199,7 @@ public class Permissions { | ||||
| 		checkInitialized(); | ||||
| 		return backendReader.getFullPermissionsList(); | ||||
| 	} | ||||
|  | ||||
| 	private Permissions() {} | ||||
| 	 | ||||
| } | ||||
| @@ -48,4 +48,5 @@ public class Reflect { | ||||
|     } | ||||
|  | ||||
|  | ||||
| 	private Reflect() {} | ||||
| } | ||||
|   | ||||
| @@ -78,4 +78,7 @@ public class WrapperRegistry { | ||||
|                                  ReflectConstructor<? extends ReflectWrapperI> objectWrapperConstructor) { | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private WrapperRegistry() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -104,4 +104,7 @@ public class DistanceUtil { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	private DistanceUtil() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -84,4 +84,6 @@ public class EnumUtil { | ||||
| 		return RandomUtil.arrayElement(enumType.getEnumConstants()); | ||||
| 	} | ||||
|  | ||||
| 	private EnumUtil() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -52,4 +52,7 @@ public class FileUtils { | ||||
| 			Files.copy(source.toPath(), target.toPath()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	private FileUtils() {} | ||||
| } | ||||
|   | ||||
| @@ -21,4 +21,8 @@ public class ListUtil { | ||||
| 			list.add(i); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	private ListUtil() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -158,5 +158,7 @@ public class MemoryUtil { | ||||
| 			unitMultiplier = uMultiplier; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private MemoryUtil() {} | ||||
| 	 | ||||
| } | ||||
|   | ||||
| @@ -97,6 +97,8 @@ public class MinecraftWebUtil { | ||||
| 		return builder.toString(); | ||||
| 		 | ||||
| 	} | ||||
|  | ||||
| 	private MinecraftWebUtil() {} | ||||
| 	 | ||||
| 	 | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import java.util.UUID; | ||||
|  * <p> | ||||
|  * You can generate the UUID programmatically using {@link #getFromNickName(String)} and | ||||
|  * {@link #getFromNickNames(String[])}. | ||||
|  * | ||||
|  * <p> | ||||
|  * To use this class as a program, type | ||||
|  * <pre> | ||||
|  *     java -cp<anyClassPathContainingThisClass> fr.pandacube.lib.util.OfflineUUID [playernames...] | ||||
| @@ -67,4 +67,6 @@ public class OfflineUUID { | ||||
| 				System.out.println(arg + "\t" + getFromNickName(arg)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private OfflineUUID() {} | ||||
| } | ||||
|   | ||||
| @@ -172,7 +172,7 @@ public class RandomUtil { | ||||
| 		return String.valueOf(pw); | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
| 	private RandomUtil() {} | ||||
| 	 | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -146,6 +146,6 @@ public class StringUtil { | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| 	private StringUtil() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -147,5 +147,6 @@ public class ThrowableUtil { | ||||
| 		return new RuntimeException(t); | ||||
| 	} | ||||
|  | ||||
| 	private ThrowableUtil() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -56,4 +56,6 @@ public class Tick { | ||||
| 		return Duration.ofMillis(toMs(tick)); | ||||
| 	} | ||||
|  | ||||
| 	private Tick() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -481,4 +481,7 @@ public class TimeUtil { | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	private TimeUtil() {} | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,9 @@ package fr.pandacube.lib.util.function; | ||||
|  | ||||
| /** | ||||
|  * A consumer that can possibly throw a checked exception. | ||||
|  * @param <T> the first parameter for this bi-consumer. | ||||
|  * @param <U> the second parameter for this bi-consumer. | ||||
|  * @param <E> the exception type that this interface method can throw. | ||||
|  */ | ||||
| @FunctionalInterface | ||||
| public interface BiConsumerException<T, U, E extends Exception> { | ||||
|   | ||||
| @@ -2,6 +2,8 @@ package fr.pandacube.lib.util.function; | ||||
|  | ||||
| /** | ||||
|  * A predicate that can possibly throw a checked exception. | ||||
|  * @param <T> the parameter for this predicate. | ||||
|  * @param <E> the exception type that this predicate can throw. | ||||
|  */ | ||||
| @FunctionalInterface | ||||
| public interface PredicateException<T, E extends Exception> { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package fr.pandacube.lib.util.function; | ||||
|  | ||||
| /** | ||||
|  * A runnable that can possibly throw a checked exception. | ||||
|  * @param <E> the exception type that this interface method can throw. | ||||
|  */ | ||||
| @FunctionalInterface | ||||
| public interface RunnableException<E extends Exception> { | ||||
|   | ||||
| @@ -2,6 +2,8 @@ package fr.pandacube.lib.util.function; | ||||
|  | ||||
| /** | ||||
|  * A supplier that can possibly throw a checked exception. | ||||
|  * @param <T> the return type of this supplier. | ||||
|  * @param <E> the exception type that this interface method can throw. | ||||
|  */ | ||||
| @FunctionalInterface | ||||
| public interface SupplierException<T, E extends Exception> { | ||||
|   | ||||
| @@ -2,6 +2,9 @@ package fr.pandacube.lib.util.function; | ||||
|  | ||||
| /** | ||||
|  * A function that can possibly throw a checked exception. | ||||
|  * @param <T> the first parameter for this bi-function. | ||||
|  * @param <U> the second parameter for this bi-function. | ||||
|  * @param <E> the exception type that this interface method can throw. | ||||
|  */ | ||||
| @FunctionalInterface | ||||
| public interface ToIntBiFunctionException<T, U, E extends Exception> { | ||||
|   | ||||
| @@ -2,6 +2,10 @@ package fr.pandacube.lib.util.function; | ||||
|  | ||||
| /** | ||||
|  * A consumer that can possibly throw a checked exception. | ||||
|  * @param <T> the first parameter for this tri-consumer. | ||||
|  * @param <U> the second parameter for this tri-consumer. | ||||
|  * @param <V> the third parameter for this tri-consumer. | ||||
|  * @param <E> the exception type that this interface method can throw. | ||||
|  */ | ||||
| @FunctionalInterface | ||||
| public interface TriConsumerException<T, U, V, E extends Exception> { | ||||
|   | ||||
| @@ -30,6 +30,11 @@ public class DailyLogRotateFileHandler extends Handler { | ||||
| 	private String currentFileDate = getCurrentDay(); | ||||
| 	private boolean closed = false; | ||||
|  | ||||
| 	/** | ||||
| 	 * Creates a new {@link DailyLogRotateFileHandler}. | ||||
| 	 */ | ||||
| 	public DailyLogRotateFileHandler() {} | ||||
|  | ||||
|  | ||||
| 	@Override | ||||
| 	public synchronized void close() throws SecurityException { | ||||
|   | ||||
| @@ -159,4 +159,6 @@ public final class Log { | ||||
| 		logger.info(message); | ||||
| 	} | ||||
|  | ||||
| 	private Log() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,11 @@ import java.time.Duration; | ||||
| public abstract class AbstractServerWS extends WebSocketAdapter implements AbstractWS { | ||||
|  | ||||
| 	private boolean isClosed = false; | ||||
|  | ||||
| 	/** | ||||
| 	 * Creates a Websocket server endpoint. | ||||
| 	 */ | ||||
| 	public AbstractServerWS() {} | ||||
| 	 | ||||
| 	@Override | ||||
| 	public final void onWebSocketConnect(Session session) | ||||
|   | ||||
| @@ -100,4 +100,6 @@ public class PayloadRegistry { | ||||
|         registerPayloadType("login-succeed", LoginSucceedPayload.class); | ||||
|     } | ||||
|  | ||||
|     private PayloadRegistry() {} | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -4,4 +4,9 @@ package fr.pandacube.lib.ws.payloads; | ||||
|  * Payload used by the server in inform the client the login was successful. | ||||
|  */ | ||||
| public class LoginSucceedPayload extends Payload { | ||||
|  | ||||
|     /** | ||||
|      * Creates a new {@link LoginSucceedPayload}. | ||||
|      */ | ||||
|     public LoginSucceedPayload() {} | ||||
| } | ||||
|   | ||||
| @@ -5,4 +5,8 @@ package fr.pandacube.lib.ws.payloads; | ||||
|  */ | ||||
| public abstract class Payload { | ||||
|  | ||||
|     /** | ||||
|      * Creates a new {@link Payload}. | ||||
|      */ | ||||
|     public Payload() {} | ||||
| } | ||||
							
								
								
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -25,7 +25,7 @@ | ||||
|         </developer> | ||||
|     </developers> | ||||
|      | ||||
|     <inceptionYear>2015</inceptionYear> | ||||
|     <inceptionYear>2011</inceptionYear> | ||||
|      | ||||
|     <ciManagement> | ||||
|         <system>jenkins</system> | ||||
| @@ -56,8 +56,8 @@ | ||||
|         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||
|          | ||||
|         <bungeecord.version>1.20-R0.3-SNAPSHOT</bungeecord.version> | ||||
|         <paper.version>1.20.4-R0.1</paper.version> | ||||
|         <mc.version>1.20.4</mc.version> | ||||
|         <paper.version>1.20.6-R0.1</paper.version> | ||||
|         <mc.version>1.20.6</mc.version> | ||||
|     </properties> | ||||
| 	 | ||||
|     <modules> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user