diff --git a/.checkstyle/checkstyle-suppressions.xml b/.checkstyle/checkstyle-suppressions.xml index dcf53640..a2f26326 100644 --- a/.checkstyle/checkstyle-suppressions.xml +++ b/.checkstyle/checkstyle-suppressions.xml @@ -4,4 +4,5 @@ "https://checkstyle.org/dtds/suppressions_1_2.dtd"> + diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a8a0bce..0e34848b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - JDA Role argument parser + - Bukkit: Implement parser for ProtoItemStack ([#257](https://github.com/Incendo/cloud/pull/257)) ### Changed - Use Command instead of TabCompleteEvent on Bukkit diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java index c3a9b459..26d84990 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitBrigadierMapper.java @@ -23,21 +23,25 @@ // package cloud.commandframework.bukkit; +import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.arguments.standard.UUIDArgument; import cloud.commandframework.brigadier.CloudBrigadierManager; -import cloud.commandframework.bukkit.arguments.selector.MultipleEntitySelector; -import cloud.commandframework.bukkit.arguments.selector.MultiplePlayerSelector; -import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector; -import cloud.commandframework.bukkit.arguments.selector.SinglePlayerSelector; -import cloud.commandframework.bukkit.parsers.location.Location2D; +import cloud.commandframework.bukkit.internal.CraftBukkitReflection; +import cloud.commandframework.bukkit.parsers.EnchantmentArgument; +import cloud.commandframework.bukkit.parsers.ItemStackArgument; +import cloud.commandframework.bukkit.parsers.location.Location2DArgument; +import cloud.commandframework.bukkit.parsers.location.LocationArgument; +import cloud.commandframework.bukkit.parsers.selector.MultipleEntitySelectorArgument; +import cloud.commandframework.bukkit.parsers.selector.MultiplePlayerSelectorArgument; +import cloud.commandframework.bukkit.parsers.selector.SingleEntitySelectorArgument; +import cloud.commandframework.bukkit.parsers.selector.SinglePlayerSelectorArgument; import com.mojang.brigadier.arguments.ArgumentType; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.enchantments.Enchantment; +import com.mojang.brigadier.arguments.StringArgumentType; +import io.leangen.geantyref.GenericTypeReflector; +import io.leangen.geantyref.TypeToken; import org.checkerframework.checker.nullness.qual.NonNull; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.UUID; import java.util.function.Supplier; import java.util.logging.Level; @@ -46,14 +50,12 @@ import java.util.logging.Level; * * @param Command sender type */ -@SuppressWarnings({"unchecked", "rawtypes"}) public final class BukkitBrigadierMapper { private static final int UUID_ARGUMENT_VERSION = 16; private final BukkitCommandManager commandManager; - private final CloudBrigadierManager brigadierManager; - private final String nmsVersion; + private final CloudBrigadierManager brigadierManager; /** @@ -64,38 +66,48 @@ public final class BukkitBrigadierMapper { */ public BukkitBrigadierMapper( final @NonNull BukkitCommandManager commandManager, - final @NonNull CloudBrigadierManager brigadierManager + final @NonNull CloudBrigadierManager brigadierManager ) { this.commandManager = commandManager; this.brigadierManager = brigadierManager; - /* Detect Minecraft Version Metadata */ - final String version = Bukkit.getServer().getClass().getPackage().getName(); - this.nmsVersion = version.substring(version.lastIndexOf(".") + 1); - final int majorMinecraftVersion = Integer.parseInt(this.nmsVersion.split("_")[1]); - - try { - /* UUID nms argument is a 1.16+ feature */ - if (majorMinecraftVersion >= UUID_ARGUMENT_VERSION) { - /* Map UUID */ - this.mapSimpleNMS(UUID.class, this.getNMSArgument("UUID").getConstructor()); - } - /* Map Enchantment */ - this.mapSimpleNMS(Enchantment.class, this.getNMSArgument("Enchantment").getConstructor()); - /* Map Entity Selectors */ - this.mapComplexNMS(SingleEntitySelector.class, this.getEntitySelectorArgument(true, false)); - this.mapComplexNMS(SinglePlayerSelector.class, this.getEntitySelectorArgument(true, true)); - this.mapComplexNMS(MultipleEntitySelector.class, this.getEntitySelectorArgument(false, false)); - this.mapComplexNMS(MultiplePlayerSelector.class, this.getEntitySelectorArgument(false, true)); - /* Map Vec3 */ - this.mapComplexNMS(Location.class, this.getArgumentVec3()); - /* Map Vec2I */ - this.mapComplexNMS(Location2D.class, this.getArgumentVec2I()); - } catch (final Exception e) { - this.commandManager.getOwningPlugin() - .getLogger() - .log(Level.WARNING, "Failed to map Bukkit types to NMS argument types", e); + if (!CraftBukkitReflection.craftBukkit()) { + this.commandManager.getOwningPlugin().getLogger().warning( + "Could not detect relocated CraftBukkit package, NMS brigadier mappings will not be enabled."); + return; } + + this.registerMappings(); + } + + private void registerMappings() { + /* UUID nms argument is a 1.16+ feature */ + if (CraftBukkitReflection.MAJOR_REVISION >= UUID_ARGUMENT_VERSION) { + /* Map UUID */ + this.mapSimpleNMS(new TypeToken>() { + }, "UUID"); + } + /* Map Enchantment */ + this.mapSimpleNMS(new TypeToken>() { + }, "Enchantment"); + /* Map ItemStackArgument */ + this.mapSimpleNMS(new TypeToken>() { + }, "ItemStack"); + /* Map Entity Selectors */ + this.mapNMS(new TypeToken>() { + }, this.entitySelectorArgumentSupplier(true, false)); + this.mapNMS(new TypeToken>() { + }, this.entitySelectorArgumentSupplier(true, true)); + this.mapNMS(new TypeToken>() { + }, this.entitySelectorArgumentSupplier(false, false)); + this.mapNMS(new TypeToken>() { + }, this.entitySelectorArgumentSupplier(false, true)); + /* Map Vec3 */ + this.mapNMS(new TypeToken>() { + }, this::argumentVec3); + /* Map Vec2I */ + this.mapNMS(new TypeToken>() { + }, this::argumentVec2i); } /** @@ -103,45 +115,39 @@ public final class BukkitBrigadierMapper { * @param playersOnly Whether the selector is for players only (true), or for all entities (false) * @return The NMS ArgumentType */ - private @NonNull Supplier> getEntitySelectorArgument( + private @NonNull Supplier> entitySelectorArgumentSupplier( final boolean single, final boolean playersOnly ) { return () -> { try { - final Constructor constructor = this.getNMSArgument("Entity").getDeclaredConstructors()[0]; + final Constructor constructor = getNMSArgument("Entity").getDeclaredConstructors()[0]; constructor.setAccessible(true); return (ArgumentType) constructor.newInstance(single, playersOnly); } catch (final Exception e) { this.commandManager.getOwningPlugin().getLogger().log(Level.INFO, "Failed to retrieve Selector Argument", e); - return null; + return fallbackType(); } }; } - @SuppressWarnings("UnnecessaryLambda") - private @NonNull Supplier> getArgumentVec3() { - return () -> { - try { - return (ArgumentType) this.getNMSArgument("Vec3").getDeclaredConstructor(boolean.class) - .newInstance(true); - } catch (final Exception e) { - this.commandManager.getOwningPlugin().getLogger().log(Level.INFO, "Failed to retrieve Vec3D argument", e); - return null; - } - }; + private @NonNull ArgumentType argumentVec3() { + try { + return (ArgumentType) getNMSArgument("Vec3").getDeclaredConstructor(boolean.class) + .newInstance(true); + } catch (final Exception e) { + this.commandManager.getOwningPlugin().getLogger().log(Level.INFO, "Failed to retrieve Vec3D argument", e); + return fallbackType(); + } } - @SuppressWarnings("UnnecessaryLambda") - private @NonNull Supplier> getArgumentVec2I() { - return () -> { - try { - return (ArgumentType) this.getNMSArgument("Vec2I").getDeclaredConstructor().newInstance(); - } catch (final Exception e) { - this.commandManager.getOwningPlugin().getLogger().log(Level.INFO, "Failed to retrieve Vec2I argument", e); - return null; - } - }; + private @NonNull ArgumentType argumentVec2i() { + try { + return (ArgumentType) getNMSArgument("Vec2I").getDeclaredConstructor().newInstance(); + } catch (final Exception e) { + this.commandManager.getOwningPlugin().getLogger().log(Level.INFO, "Failed to retrieve Vec2I argument", e); + return fallbackType(); + } } /** @@ -149,40 +155,52 @@ public final class BukkitBrigadierMapper { * * @param argument Argument type name * @return Argument class - * @throws Exception If the type cannot be retrieved + * @throws RuntimeException when the class is not found */ - @NonNull - private Class getNMSArgument(final @NonNull String argument) throws Exception { - return Class.forName(String.format("net.minecraft.server.%s.Argument%s", this.nmsVersion, argument)); + private static @NonNull Class getNMSArgument(final @NonNull String argument) throws RuntimeException { + return CraftBukkitReflection.needNMSClass("Argument" + argument); } /** - * Attempt to register a mapping between a type and a NMS argument type + * Attempt to register a mapping between a cloud argument parser type and an NMS brigadier argument type which + * has a no-args constructor. * - * @param type Type to map - * @param constructor Constructor that construct the NMS argument type + * @param type Type to map + * @param argument parser type + * @param argumentName NMS Argument class name (without 'Argument' prefix) + * @since 1.5.0 */ - public void mapSimpleNMS( - final @NonNull Class type, - final @NonNull Constructor constructor + public > void mapSimpleNMS( + final @NonNull TypeToken type, + final @NonNull String argumentName ) { + final Constructor constructor; try { - this.brigadierManager.registerDefaultArgumentTypeSupplier(type, () -> { - try { - return constructor.newInstance(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - e.printStackTrace(); - } - return null; - }); - } catch (final Exception e) { - this.commandManager.getOwningPlugin() - .getLogger() - .warning(String.format( - "Failed to map '%s' to a Mojang serializable argument type", - type.getCanonicalName() - )); + final Class nmsArgument = getNMSArgument(argumentName); + constructor = nmsArgument.getConstructor(); + } catch (final RuntimeException | ReflectiveOperationException e) { + this.commandManager.getOwningPlugin().getLogger().log( + Level.WARNING, + String.format("Failed to create mapping for NMS brigadier argument type '%s'.", argumentName), + e + ); + return; } + this.brigadierManager.registerMapping(type, builder -> builder.to(argument -> { + try { + return (ArgumentType) constructor.newInstance(); + } catch (final ReflectiveOperationException e) { + this.commandManager.getOwningPlugin().getLogger().log( + Level.WARNING, + String.format( + "Failed to create instance of brigadier argument type '%s'.", + GenericTypeReflector.erase(type.getType()).getCanonicalName() + ), + e + ); + return fallbackType(); + } + })); } /** @@ -190,21 +208,61 @@ public final class BukkitBrigadierMapper { * * @param type Type to map * @param argumentTypeSupplier Supplier of the NMS argument type + * @param argument parser type + * @since 1.5.0 */ + public > void mapNMS( + final @NonNull TypeToken type, + final @NonNull Supplier> argumentTypeSupplier + ) { + this.brigadierManager.registerMapping(type, builder -> + builder.to(argument -> argumentTypeSupplier.get()) + ); + } + + private static @NonNull StringArgumentType fallbackType() { + return StringArgumentType.word(); + } + + /** + * Attempt to register a mapping between a type and a NMS argument type + * + * @param type Type to map + * @param constructor Constructor that construct the NMS argument type + * @deprecated use {@link #mapSimpleNMS(TypeToken, String)} instead + */ + @Deprecated + public void mapSimpleNMS( + final @NonNull Class type, + final @NonNull Constructor constructor + ) { + this.brigadierManager.registerDefaultArgumentTypeSupplier(type, () -> { + try { + return (ArgumentType) constructor.newInstance(); + } catch (final ReflectiveOperationException e) { + this.commandManager.getOwningPlugin().getLogger().log( + Level.WARNING, + String.format("Failed to map brigadier argument type '%s'", type.getCanonicalName()), + e + ); + return fallbackType(); + } + }); + } + + /** + * Attempt to register a mapping between a type and a NMS argument type + * + * @param type Type to map + * @param argumentTypeSupplier Supplier of the NMS argument type + * @deprecated use {@link #mapNMS(TypeToken, Supplier)} instead + */ + @Deprecated public void mapComplexNMS( final @NonNull Class type, final @NonNull Supplier> argumentTypeSupplier ) { - try { - this.brigadierManager.registerDefaultArgumentTypeSupplier(type, argumentTypeSupplier); - } catch (final Exception e) { - this.commandManager.getOwningPlugin() - .getLogger() - .warning(String.format( - "Failed to map '%s' to a Mojang serializable argument type", - type.getCanonicalName() - )); - } + this.brigadierManager.registerDefaultArgumentTypeSupplier(type, argumentTypeSupplier); } } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandContextKeys.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandContextKeys.java new file mode 100644 index 00000000..88471a97 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandContextKeys.java @@ -0,0 +1,64 @@ +// +// MIT License +// +// Copyright (c) 2021 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.bukkit; + +import cloud.commandframework.keys.CloudKey; +import cloud.commandframework.keys.SimpleCloudKey; +import io.leangen.geantyref.TypeToken; +import org.bukkit.command.CommandSender; + +import java.util.Set; + +/** + * Bukkit related {@link cloud.commandframework.context.CommandContext} keys. + * + * @since 1.5.0 + */ +public final class BukkitCommandContextKeys { + + /** + * Key used to store the Bukkit native {@link CommandSender} in the {@link cloud.commandframework.context.CommandContext}. + * + * @since 1.5.0 + */ + public static final CloudKey BUKKIT_COMMAND_SENDER = SimpleCloudKey.of( + "BukkitCommandSender", + TypeToken.get(CommandSender.class) + ); + + /** + * Key used to store the active {@link CloudBukkitCapabilities} in the {@link cloud.commandframework.context.CommandContext}. + * + * @since 1.5.0 + */ + public static final CloudKey> CLOUD_BUKKIT_CAPABILITIES = SimpleCloudKey.of( + "CloudBukkitCapabilities", + new TypeToken>() { + } + ); + + private BukkitCommandContextKeys() { + } + +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java index bcb769b9..fc49b404 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandManager.java @@ -31,7 +31,10 @@ import cloud.commandframework.bukkit.arguments.selector.MultipleEntitySelector; import cloud.commandframework.bukkit.arguments.selector.MultiplePlayerSelector; import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector; import cloud.commandframework.bukkit.arguments.selector.SinglePlayerSelector; +import cloud.commandframework.bukkit.data.ProtoItemStack; +import cloud.commandframework.bukkit.internal.CraftBukkitReflection; import cloud.commandframework.bukkit.parsers.EnchantmentArgument; +import cloud.commandframework.bukkit.parsers.ItemStackArgument; import cloud.commandframework.bukkit.parsers.MaterialArgument; import cloud.commandframework.bukkit.parsers.OfflinePlayerArgument; import cloud.commandframework.bukkit.parsers.PlayerArgument; @@ -81,7 +84,7 @@ public class BukkitCommandManager extends CommandManager implements Brigad private final Plugin owningPlugin; private final int minecraftVersion; - private final boolean paper; + private final boolean paper = CraftBukkitReflection.classExists("com.destroystokyo.paper.PaperConfig"); private final Function commandSenderMapper; private final Function backwardsCommandSenderMapper; @@ -134,30 +137,7 @@ public class BukkitCommandManager extends CommandManager implements Brigad this.taskFactory = new TaskFactory(bukkitSynchronizer); /* Try to determine the Minecraft version */ - int version = -1; - try { - final Matcher matcher = Pattern.compile("\\(MC: (\\d)\\.(\\d+)\\.?(\\d+?)?\\)") - .matcher(Bukkit.getVersion()); - if (matcher.find()) { - version = Integer.parseInt( - matcher.toMatchResult().group(2), - VERSION_RADIX - ); - } - } catch (final Exception e) { - this.owningPlugin.getLogger().severe("Failed to determine Minecraft version " - + "for cloud Bukkit capability detection"); - } - this.minecraftVersion = version; - - boolean paper = false; - try { - Class.forName("com.destroystokyo.paper.PaperConfig"); - paper = true; - } catch (final Exception ignored) { - // This is fine - } - this.paper = paper; + this.minecraftVersion = this.getMinecraftVersion(); /* Register Bukkit Preprocessor */ this.registerCommandPreProcessor(new BukkitCommandPreprocessor<>(this)); @@ -177,6 +157,8 @@ public class BukkitCommandManager extends CommandManager implements Brigad new LocationArgument.LocationParser<>()); this.getParserRegistry().registerParserSupplier(TypeToken.get(Location2D.class), parserParameters -> new Location2DArgument.Location2DParser<>()); + this.getParserRegistry().registerParserSupplier(TypeToken.get(ProtoItemStack.class), parserParameters -> + new ItemStackArgument.Parser<>()); /* Register Entity Selector Parsers */ this.getParserRegistry().registerParserSupplier(TypeToken.get(SingleEntitySelector.class), parserParameters -> new SingleEntitySelectorArgument.SingleEntitySelectorParser<>()); @@ -196,6 +178,19 @@ public class BukkitCommandManager extends CommandManager implements Brigad this.setCaptionRegistry(new BukkitCaptionRegistryFactory().create()); } + private int getMinecraftVersion() { + try { + final Matcher matcher = Pattern.compile("\\(MC: (\\d)\\.(\\d+)\\.?(\\d+?)?\\)").matcher(Bukkit.getVersion()); + if (matcher.find()) { + return Integer.parseInt(matcher.toMatchResult().group(2), VERSION_RADIX); + } + } catch (final Exception e) { + this.owningPlugin.getLogger() + .severe("Failed to determine Minecraft version for cloud Bukkit capability detection"); + } + return -1; + } + /** * Create a command manager using Bukkit's {@link CommandSender} as the sender type. * diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandPreprocessor.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandPreprocessor.java index 32f8e596..563bd890 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandPreprocessor.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/BukkitCommandPreprocessor.java @@ -27,6 +27,8 @@ import cloud.commandframework.execution.preprocessor.CommandPreprocessingContext import cloud.commandframework.execution.preprocessor.CommandPreprocessor; import org.checkerframework.checker.nullness.qual.NonNull; +import java.util.Set; + /** * Command preprocessor which decorates incoming {@link cloud.commandframework.context.CommandContext} * with Bukkit specific objects @@ -35,26 +37,29 @@ import org.checkerframework.checker.nullness.qual.NonNull; */ final class BukkitCommandPreprocessor implements CommandPreprocessor { - private final BukkitCommandManager mgr; + private final BukkitCommandManager commandManager; + private final Set bukkitCapabilities; /** * The Bukkit Command Preprocessor for storing Bukkit-specific contexts in the command contexts * - * @param mgr The BukkitCommandManager + * @param commandManager The BukkitCommandManager */ - BukkitCommandPreprocessor(final @NonNull BukkitCommandManager mgr) { - this.mgr = mgr; + BukkitCommandPreprocessor(final @NonNull BukkitCommandManager commandManager) { + this.commandManager = commandManager; + this.bukkitCapabilities = commandManager.queryCapabilities(); } - /** - * Stores the sender mapped to {@link org.bukkit.command.CommandSender} in the context with the key "BukkitCommandSender", - * and a {@link java.util.Set} of {@link CloudBukkitCapabilities} with the key "CloudBukkitCapabilities" - */ @Override public void accept(final @NonNull CommandPreprocessingContext context) { - context.getCommandContext().store("BukkitCommandSender", this.mgr.getBackwardsCommandSenderMapper().apply( - context.getCommandContext().getSender())); - context.getCommandContext().store("CloudBukkitCapabilities", this.mgr.queryCapabilities()); + context.getCommandContext().store( + BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER, + this.commandManager.getBackwardsCommandSenderMapper().apply(context.getCommandContext().getSender()) + ); + context.getCommandContext().store( + BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES, + this.bukkitCapabilities + ); } } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/CloudCommodoreManager.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/CloudCommodoreManager.java index 15b04bba..aa684b9e 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/CloudCommodoreManager.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/CloudCommodoreManager.java @@ -25,8 +25,8 @@ package cloud.commandframework.bukkit; import cloud.commandframework.Command; import cloud.commandframework.brigadier.CloudBrigadierManager; +import cloud.commandframework.bukkit.internal.CraftBukkitReflection; import cloud.commandframework.context.CommandContext; -import cloud.commandframework.permission.CommandPermission; import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import me.lucko.commodore.Commodore; @@ -36,13 +36,14 @@ import org.bukkit.command.CommandSender; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import java.lang.reflect.Method; import java.util.Collections; @SuppressWarnings({"unchecked", "rawtypes"}) class CloudCommodoreManager extends BukkitPluginRegistrationHandler { private final BukkitCommandManager commandManager; - private final CloudBrigadierManager brigadierManager; + private final CloudBrigadierManager brigadierManager; private final Commodore commodore; CloudCommodoreManager(final @NonNull BukkitCommandManager commandManager) @@ -53,17 +54,29 @@ class CloudCommodoreManager extends BukkitPluginRegistrationHandler { } this.commandManager = commandManager; this.commodore = CommodoreProvider.getCommodore(commandManager.getOwningPlugin()); - this.brigadierManager = new CloudBrigadierManager<>(commandManager, () -> - new CommandContext<>( - commandManager.getCommandSenderMapper().apply(Bukkit.getConsoleSender()), - commandManager - )); - this.brigadierManager.brigadierSenderMapper( - sender -> this.commandManager.getCommandSenderMapper().apply( - this.commodore.getBukkitSender(sender) - ) - ); + this.brigadierManager = new CloudBrigadierManager<>(commandManager, () -> new CommandContext<>( + commandManager.getCommandSenderMapper().apply(Bukkit.getConsoleSender()), + commandManager + )); + + this.brigadierManager.brigadierSenderMapper(sender -> + this.commandManager.getCommandSenderMapper().apply(this.commodore.getBukkitSender(sender))); + new BukkitBrigadierMapper<>(this.commandManager, this.brigadierManager); + + if (!CraftBukkitReflection.craftBukkit()) { + return; + } + final Class vanillaCommandWrapperClass = CraftBukkitReflection.needOBCClass("command.VanillaCommandWrapper"); + final Method getListenerMethod = CraftBukkitReflection.needMethod( + vanillaCommandWrapperClass, "getListener", CommandSender.class); + this.brigadierManager.backwardsBrigadierSenderMapper(cloud -> { + try { + return getListenerMethod.invoke(null, this.commandManager.getBackwardsCommandSenderMapper().apply(cloud)); + } catch (final ReflectiveOperationException e) { + throw new RuntimeException(e); + } + }); } @Override @@ -72,7 +85,7 @@ class CloudCommodoreManager extends BukkitPluginRegistrationHandler { final @NonNull Command command, final @NonNull BukkitCommand bukkitCommand ) { - this.registerWithCommodore(label, command); + this.registerWithCommodore(label, (Command) command); } protected @NonNull CloudBrigadierManager brigadierManager() { @@ -81,17 +94,13 @@ class CloudCommodoreManager extends BukkitPluginRegistrationHandler { private void registerWithCommodore( final @NonNull String label, - final @NonNull Command command + final @NonNull Command command ) { - final com.mojang.brigadier.Command cmd = o -> 1; final LiteralCommandNode literalCommandNode = this.brigadierManager .createLiteralCommandNode(label, command, (o, p) -> { final CommandSender sender = this.commodore.getBukkitSender(o); - return this.commandManager.hasPermission( - this.commandManager.getCommandSenderMapper().apply(sender), - (CommandPermission) p - ); - }, false, cmd); + return this.commandManager.hasPermission(this.commandManager.getCommandSenderMapper().apply(sender), p); + }, false, o -> 1); final CommandNode existingNode = this.commodore.getDispatcher().findNode(Collections.singletonList(label)); if (existingNode != null) { this.mergeChildren(existingNode, literalCommandNode); diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/data/ProtoItemStack.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/data/ProtoItemStack.java new file mode 100644 index 00000000..ee2e07c5 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/data/ProtoItemStack.java @@ -0,0 +1,63 @@ +// +// MIT License +// +// Copyright (c) 2021 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.bukkit.data; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Intermediary result for an argument which parses a {@link Material} and optional NBT data. + * + * @since 1.5.0 + */ +public interface ProtoItemStack { + + /** + * Get the {@link Material} of this {@link ProtoItemStack}. + * + * @return the {@link Material} + * @since 1.5.0 + */ + @NonNull Material material(); + + /** + * Get whether this {@link ProtoItemStack} contains extra data besides the {@link Material}. + * + * @return whether there is extra data + */ + boolean hasExtraData(); + + /** + * Create a new {@link ItemStack} from the state of this {@link ProtoItemStack}. + * + * @param stackSize stack size + * @param respectMaximumStackSize whether to respect the maximum stack size for the material + * @return the created {@link ItemStack} + * @throws IllegalArgumentException if the {@link ItemStack} could not be created, due to max stack size or other reasons + */ + @NonNull ItemStack createItemStack(int stackSize, boolean respectMaximumStackSize) + throws IllegalArgumentException; + +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/data/package-info.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/data/package-info.java new file mode 100644 index 00000000..05654e91 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/data/package-info.java @@ -0,0 +1,27 @@ +// +// MIT License +// +// Copyright (c) 2021 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +/** + * cloud-bukkit data holders + */ +package cloud.commandframework.bukkit.data; diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/internal/CraftBukkitReflection.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/internal/CraftBukkitReflection.java new file mode 100644 index 00000000..2ab3d085 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/internal/CraftBukkitReflection.java @@ -0,0 +1,126 @@ +// +// MIT License +// +// Copyright (c) 2021 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.bukkit.internal; + +import com.google.common.annotations.Beta; +import org.bukkit.Bukkit; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Utilities for doing reflection on CraftBukkit, used by the cloud implementation. + * + *

This is not API to any extent, and as such, may break, change, or be removed without any notice.

+ */ +@Beta +public final class CraftBukkitReflection { + + private static final String PREFIX_NMS = "net.minecraft.server"; + private static final String PREFIX_CRAFTBUKKIT = "org.bukkit.craftbukkit"; + private static final String CRAFT_SERVER = "CraftServer"; + private static final String VERSION; + public static final int MAJOR_REVISION; + + static { + final Class serverClass = Bukkit.getServer().getClass(); + final String pkg = serverClass.getPackage().getName(); + final String nmsVersion = pkg.substring(pkg.lastIndexOf(".") + 1); + if (!nmsVersion.contains("_")) { + MAJOR_REVISION = -1; + VERSION = null; + } else { + MAJOR_REVISION = Integer.parseInt(nmsVersion.split("_")[1]); + String name = serverClass.getName(); + name = name.substring(PREFIX_CRAFTBUKKIT.length()); + name = name.substring(0, name.length() - CRAFT_SERVER.length()); + VERSION = name; + } + } + + public static boolean craftBukkit() { + return MAJOR_REVISION != -1 && VERSION != null; + } + + public static @NonNull Class needNMSClass(final @NonNull String className) throws RuntimeException { + return needClass(PREFIX_NMS + VERSION + className); + } + + public static @NonNull Class needOBCClass(final @NonNull String className) throws RuntimeException { + return needClass(PREFIX_CRAFTBUKKIT + VERSION + className); + } + + public static @NonNull Class needClass(final @NonNull String className) throws RuntimeException { + try { + return Class.forName(className); + } catch (final ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public static @Nullable Class findClass(final @NonNull String className) { + try { + return Class.forName(className); + } catch (final ClassNotFoundException e) { + return null; + } + } + + public static @NonNull Field needField(final @NonNull Class holder, final @NonNull String name) throws RuntimeException { + try { + final Field field = holder.getDeclaredField(name); + field.setAccessible(true); + return field; + } catch (final ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + public static boolean classExists(final @NonNull String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + public static @NonNull Method needMethod( + final @NonNull Class holder, + final @NonNull String name, + final @NonNull Class... params + ) throws RuntimeException { + try { + return holder.getMethod(name, params); + } catch (final NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private CraftBukkitReflection() { + } + +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/ItemStackArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/ItemStackArgument.java new file mode 100644 index 00000000..d8c9f7c2 --- /dev/null +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/ItemStackArgument.java @@ -0,0 +1,331 @@ +// +// MIT License +// +// Copyright (c) 2021 Alexander Söderberg & Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +package cloud.commandframework.bukkit.parsers; + +import cloud.commandframework.ArgumentDescription; +import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.parser.ArgumentParseResult; +import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.brigadier.argument.WrappedBrigadierParser; +import cloud.commandframework.bukkit.BukkitCommandManager; +import cloud.commandframework.bukkit.data.ProtoItemStack; +import cloud.commandframework.bukkit.internal.CraftBukkitReflection; +import cloud.commandframework.context.CommandContext; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Queue; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +/** + * Argument type for parsing a {@link Material} and optional extra NBT data into a {@link ProtoItemStack}. + * + *

This argument type only provides basic suggestions by default. On Minecraft 1.13 and newer, enabling Brigadier + * compatibility through {@link BukkitCommandManager#registerBrigadier()} will allow client side validation and + * suggestions to be utilized.

+ * + * @param Command sender type + * @since 1.5.0 + */ +public final class ItemStackArgument extends CommandArgument { + + private ItemStackArgument( + final boolean required, + final @NonNull String name, + final @NonNull String defaultValue, + final @Nullable BiFunction<@NonNull CommandContext, @NonNull String, + @NonNull List<@NonNull String>> suggestionsProvider, + final @NonNull ArgumentDescription defaultDescription + ) { + super(required, name, new Parser<>(), defaultValue, ProtoItemStack.class, suggestionsProvider, defaultDescription); + } + + /** + * Create a new {@link Builder}. + * + * @param name Name of the argument + * @param Command sender type + * @return Created builder + * @since 1.5.0 + */ + public static ItemStackArgument.@NonNull Builder builder(final @NonNull String name) { + return new ItemStackArgument.Builder<>(name); + } + + /** + * Create a new required {@link ItemStackArgument}. + * + * @param name Argument name + * @param Command sender type + * @return Created argument + * @since 1.5.0 + */ + public static @NonNull CommandArgument of(final @NonNull String name) { + return ItemStackArgument.builder(name).build(); + } + + /** + * Create a new optional {@link ItemStackArgument}. + * + * @param name Argument name + * @param Command sender type + * @return Created argument + * @since 1.5.0 + */ + public static @NonNull CommandArgument optional(final @NonNull String name) { + return ItemStackArgument.builder(name).asOptional().build(); + } + + + /** + * Builder for {@link ItemStackArgument}. + * + * @param sender type + * @since 1.5.0 + */ + public static final class Builder extends TypedBuilder> { + + private Builder(final @NonNull String name) { + super(ProtoItemStack.class, name); + } + + @Override + public @NonNull ItemStackArgument build() { + return new ItemStackArgument<>( + this.isRequired(), + this.getName(), + this.getDefaultValue(), + this.getSuggestionsProvider(), + this.getDefaultDescription() + ); + } + + } + + /** + * Parser for {@link ProtoItemStack}. Requires a CraftBukkit based server implementation. + * + * @param sender type + * @since 1.5.0 + */ + public static final class Parser implements ArgumentParser { + + private final ArgumentParser parser; + + /** + * Create a new {@link Parser}. + * + * @since 1.5.0 + */ + public Parser() { + if (!CraftBukkitReflection.craftBukkit()) { + throw new UnsupportedOperationException("ItemStack parser requires CraftBukkit"); + } + if (CraftBukkitReflection.MAJOR_REVISION >= 13) { + this.parser = new ModernParser<>(); + } else { + this.parser = new LegacyParser<>(); + } + } + + @Override + public @NonNull ArgumentParseResult parse( + final @NonNull CommandContext commandContext, + final @NonNull Queue<@NonNull String> inputQueue + ) { + return this.parser.parse(commandContext, inputQueue); + } + + @Override + public @NonNull List<@NonNull String> suggestions( + final @NonNull CommandContext commandContext, + final @NonNull String input + ) { + return Arrays.stream(Material.values()) + .map(value -> value.name().toLowerCase(Locale.ROOT)) + .collect(Collectors.toList()); + } + + } + + private static final class ModernParser implements ArgumentParser { + + private static final Class NMS_ITEM_STACK_CLASS = CraftBukkitReflection.needNMSClass("ItemStack"); + private static final Class CRAFT_ITEM_STACK_CLASS = + CraftBukkitReflection.needOBCClass("inventory.CraftItemStack"); + private static final Class ARGUMENT_ITEM_STACK_CLASS = + CraftBukkitReflection.needNMSClass("ArgumentItemStack"); + private static final Class ARGUMENT_PREDICATE_ITEM_STACK_CLASS = + CraftBukkitReflection.needNMSClass("ArgumentPredicateItemStack"); + private static final Class NMS_ITEM_CLASS = CraftBukkitReflection.needNMSClass("Item"); + private static final Class CRAFT_MAGIC_NUMBERS_CLASS = + CraftBukkitReflection.needOBCClass("util.CraftMagicNumbers"); + private static final Method GET_MATERIAL_METHOD = CraftBukkitReflection + .needMethod(CRAFT_MAGIC_NUMBERS_CLASS, "getMaterial", NMS_ITEM_CLASS); + private static final Method CREATE_ITEM_STACK_METHOD = CraftBukkitReflection + .needMethod(ARGUMENT_PREDICATE_ITEM_STACK_CLASS, "a", int.class, boolean.class); + private static final Method AS_BUKKIT_COPY_METHOD = CraftBukkitReflection + .needMethod(CRAFT_ITEM_STACK_CLASS, "asBukkitCopy", NMS_ITEM_STACK_CLASS); + private static final Field ITEM_FIELD = CraftBukkitReflection.needField(ARGUMENT_PREDICATE_ITEM_STACK_CLASS, "b"); + private static final Field COMPOUND_TAG_FIELD = CraftBukkitReflection.needField(ARGUMENT_PREDICATE_ITEM_STACK_CLASS, "c"); + + private final ArgumentParser parser; + + ModernParser() { + try { + this.parser = this.createParser(); + } catch (final ReflectiveOperationException ex) { + throw new RuntimeException("Failed to initialize modern ItemStack parser.", ex); + } + } + + @SuppressWarnings("unchecked") + private ArgumentParser createParser() throws ReflectiveOperationException { + return new WrappedBrigadierParser( + (ArgumentType) ARGUMENT_ITEM_STACK_CLASS.getConstructor().newInstance() + ).map((ctx, itemInput) -> ArgumentParseResult.success(new ModernProtoItemStack(itemInput))); + } + + @Override + public @NonNull ArgumentParseResult<@NonNull ProtoItemStack> parse( + @NonNull final CommandContext<@NonNull C> commandContext, + @NonNull final Queue<@NonNull String> inputQueue + ) { + // Minecraft has a parser for this - just use it + return this.parser.parse(commandContext, inputQueue); + } + + private static final class ModernProtoItemStack implements ProtoItemStack { + + private final Object itemInput; + private final Material material; + private final @Nullable String snbt; + + ModernProtoItemStack(final @NonNull Object itemInput) { + this.itemInput = itemInput; + try { + this.material = (Material) GET_MATERIAL_METHOD.invoke(null, ITEM_FIELD.get(itemInput)); + final Object compoundTag = COMPOUND_TAG_FIELD.get(itemInput); + if (compoundTag != null) { + this.snbt = compoundTag.toString(); + } else { + this.snbt = null; + } + } catch (final ReflectiveOperationException ex) { + throw new RuntimeException(ex); + } + } + + @Override + public @NonNull Material material() { + return this.material; + } + + @Override + public boolean hasExtraData() { + return this.snbt != null; + } + + @Override + public @NonNull ItemStack createItemStack(final int stackSize, final boolean respectMaximumStackSize) { + try { + return (ItemStack) AS_BUKKIT_COPY_METHOD.invoke( + null, + CREATE_ITEM_STACK_METHOD.invoke(this.itemInput, stackSize, respectMaximumStackSize) + ); + } catch (final InvocationTargetException ex) { + final Throwable cause = ex.getCause(); + if (cause instanceof CommandSyntaxException) { + throw new IllegalArgumentException(cause.getMessage(), cause); + } + throw new RuntimeException(ex); + } catch (final ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + } + + } + + private static final class LegacyParser implements ArgumentParser { + + private final ArgumentParser parser = new MaterialArgument.MaterialParser() + .map((ctx, material) -> ArgumentParseResult.success(new LegacyProtoItemStack(material))); + + @Override + public @NonNull ArgumentParseResult<@NonNull ProtoItemStack> parse( + @NonNull final CommandContext<@NonNull C> commandContext, + @NonNull final Queue<@NonNull String> inputQueue + ) { + return this.parser.parse(commandContext, inputQueue); + } + + private static final class LegacyProtoItemStack implements ProtoItemStack { + + private final Material material; + + private LegacyProtoItemStack(final @NonNull Material material) { + this.material = material; + } + + @Override + public @NonNull Material material() { + return this.material; + } + + @Override + public boolean hasExtraData() { + return false; + } + + @Override + public @NonNull ItemStack createItemStack(final int stackSize, final boolean respectMaximumStackSize) + throws IllegalArgumentException { + if (respectMaximumStackSize && stackSize > this.material.getMaxStackSize()) { + throw new IllegalArgumentException(String.format( + "The maximum stack size for %s is %d", + this.material, + this.material.getMaxStackSize() + )); + } + return new ItemStack(this.material, stackSize); + } + + } + + } + +} diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/OfflinePlayerArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/OfflinePlayerArgument.java index 49f5a7df..59f782da 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/OfflinePlayerArgument.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/OfflinePlayerArgument.java @@ -28,12 +28,14 @@ import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.bukkit.BukkitCaptionKeys; +import cloud.commandframework.bukkit.BukkitCommandContextKeys; import cloud.commandframework.captions.CaptionVariable; import cloud.commandframework.context.CommandContext; import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import cloud.commandframework.exceptions.parsing.ParserException; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -177,6 +179,10 @@ public final class OfflinePlayerArgument extends CommandArgument output = new ArrayList<>(); for (Player player : Bukkit.getOnlinePlayers()) { + final CommandSender bukkit = commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER); + if (bukkit instanceof Player && !((Player) bukkit).canSee(player)) { + continue; + } output.add(player.getName()); } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/PlayerArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/PlayerArgument.java index d00f3594..fe78755a 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/PlayerArgument.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/PlayerArgument.java @@ -28,11 +28,13 @@ import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.bukkit.BukkitCaptionKeys; +import cloud.commandframework.bukkit.BukkitCommandContextKeys; import cloud.commandframework.captions.CaptionVariable; import cloud.commandframework.context.CommandContext; import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import cloud.commandframework.exceptions.parsing.ParserException; import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -169,6 +171,10 @@ public final class PlayerArgument extends CommandArgument { List output = new ArrayList<>(); for (Player player : Bukkit.getOnlinePlayers()) { + final CommandSender bukkit = commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER); + if (bukkit instanceof Player && !((Player) bukkit).canSee(player)) { + continue; + } output.add(player.getName()); } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DArgument.java index 57d12451..4969f38b 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DArgument.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/Location2DArgument.java @@ -27,6 +27,7 @@ import cloud.commandframework.ArgumentDescription; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.bukkit.BukkitCommandContextKeys; import cloud.commandframework.bukkit.parsers.location.LocationArgument.LocationParseException; import cloud.commandframework.context.CommandContext; import io.leangen.geantyref.TypeToken; @@ -183,7 +184,7 @@ public final class Location2DArgument extends CommandArgument coordinates[i] = coordinate.getParsedValue().orElseThrow(NullPointerException::new); } final Location originalLocation; - final CommandSender bukkitSender = commandContext.get("BukkitCommandSender"); + final CommandSender bukkitSender = commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER); if (bukkitSender instanceof BlockCommandSender) { originalLocation = ((BlockCommandSender) bukkitSender).getBlock().getLocation(); diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationArgument.java index 30b3809d..b9fe9da7 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationArgument.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/location/LocationArgument.java @@ -29,6 +29,7 @@ import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.bukkit.BukkitCaptionKeys; +import cloud.commandframework.bukkit.BukkitCommandContextKeys; import cloud.commandframework.captions.Caption; import cloud.commandframework.captions.CaptionVariable; import cloud.commandframework.context.CommandContext; @@ -191,7 +192,7 @@ public final class LocationArgument extends CommandArgument { coordinates[i] = coordinate.getParsedValue().orElseThrow(NullPointerException::new); } final Location originalLocation; - final CommandSender bukkitSender = commandContext.get("BukkitCommandSender"); + final CommandSender bukkitSender = commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER); if (bukkitSender instanceof BlockCommandSender) { originalLocation = ((BlockCommandSender) bukkitSender).getBlock().getLocation(); diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/MultipleEntitySelectorArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/MultipleEntitySelectorArgument.java index c4e63be4..37b0e46d 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/MultipleEntitySelectorArgument.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/MultipleEntitySelectorArgument.java @@ -27,6 +27,7 @@ import cloud.commandframework.ArgumentDescription; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.bukkit.BukkitCommandContextKeys; import cloud.commandframework.bukkit.CloudBukkitCapabilities; import cloud.commandframework.bukkit.arguments.selector.MultipleEntitySelector; import cloud.commandframework.context.CommandContext; @@ -38,7 +39,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.util.List; import java.util.Queue; -import java.util.Set; import java.util.function.BiFunction; public final class MultipleEntitySelectorArgument extends CommandArgument { @@ -133,7 +133,7 @@ public final class MultipleEntitySelectorArgument extends CommandArgument commandContext, final @NonNull Queue<@NonNull String> inputQueue ) { - if (!commandContext.>get("CloudBukkitCapabilities").contains( + if (!commandContext.get(BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES).contains( CloudBukkitCapabilities.BRIGADIER)) { return ArgumentParseResult.failure(new SelectorParseException( "", @@ -153,7 +153,7 @@ public final class MultipleEntitySelectorArgument extends CommandArgument entities; try { - entities = Bukkit.selectEntities(commandContext.get("BukkitCommandSender"), input); + entities = Bukkit.selectEntities(commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER), input); } catch (IllegalArgumentException e) { return ArgumentParseResult.failure(new SelectorParseException( input, diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/MultiplePlayerSelectorArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/MultiplePlayerSelectorArgument.java index 54e8a25f..857e4d48 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/MultiplePlayerSelectorArgument.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/MultiplePlayerSelectorArgument.java @@ -27,6 +27,7 @@ import cloud.commandframework.ArgumentDescription; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.bukkit.BukkitCommandContextKeys; import cloud.commandframework.bukkit.CloudBukkitCapabilities; import cloud.commandframework.bukkit.arguments.selector.MultiplePlayerSelector; import cloud.commandframework.bukkit.parsers.PlayerArgument; @@ -34,6 +35,7 @@ import cloud.commandframework.context.CommandContext; import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import com.google.common.collect.ImmutableList; import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.checkerframework.checker.nullness.qual.NonNull; @@ -42,7 +44,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Queue; -import java.util.Set; import java.util.function.BiFunction; public final class MultiplePlayerSelectorArgument extends CommandArgument { @@ -146,7 +147,7 @@ public final class MultiplePlayerSelectorArgument extends CommandArgument>get("CloudBukkitCapabilities").contains( + if (!commandContext.get(BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES).contains( CloudBukkitCapabilities.BRIGADIER)) { @SuppressWarnings("deprecation") Player player = Bukkit.getPlayer(input); @@ -159,7 +160,7 @@ public final class MultiplePlayerSelectorArgument extends CommandArgument entities; try { - entities = Bukkit.selectEntities(commandContext.get("BukkitCommandSender"), input); + entities = Bukkit.selectEntities(commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER), input); } catch (IllegalArgumentException e) { return ArgumentParseResult.failure(new SelectorParseException( input, @@ -191,6 +192,10 @@ public final class MultiplePlayerSelectorArgument extends CommandArgument output = new ArrayList<>(); for (Player player : Bukkit.getOnlinePlayers()) { + final CommandSender bukkit = commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER); + if (bukkit instanceof Player && !((Player) bukkit).canSee(player)) { + continue; + } output.add(player.getName()); } diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SingleEntitySelectorArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SingleEntitySelectorArgument.java index 818d690e..4f88a4a0 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SingleEntitySelectorArgument.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SingleEntitySelectorArgument.java @@ -27,6 +27,7 @@ import cloud.commandframework.ArgumentDescription; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.bukkit.BukkitCommandContextKeys; import cloud.commandframework.bukkit.CloudBukkitCapabilities; import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector; import cloud.commandframework.context.CommandContext; @@ -38,7 +39,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.util.List; import java.util.Queue; -import java.util.Set; import java.util.function.BiFunction; public final class SingleEntitySelectorArgument extends CommandArgument { @@ -139,7 +139,7 @@ public final class SingleEntitySelectorArgument extends CommandArgument commandContext, final @NonNull Queue<@NonNull String> inputQueue ) { - if (!commandContext.>get("CloudBukkitCapabilities").contains( + if (!commandContext.get(BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES).contains( CloudBukkitCapabilities.BRIGADIER)) { return ArgumentParseResult.failure(new SelectorParseException( "", @@ -159,7 +159,7 @@ public final class SingleEntitySelectorArgument extends CommandArgument entities; try { - entities = Bukkit.selectEntities(commandContext.get("BukkitCommandSender"), input); + entities = Bukkit.selectEntities(commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER), input); } catch (IllegalArgumentException e) { return ArgumentParseResult.failure(new SelectorParseException( input, diff --git a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SinglePlayerSelectorArgument.java b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SinglePlayerSelectorArgument.java index 1ebd1a0d..d94f8e9f 100644 --- a/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SinglePlayerSelectorArgument.java +++ b/cloud-minecraft/cloud-bukkit/src/main/java/cloud/commandframework/bukkit/parsers/selector/SinglePlayerSelectorArgument.java @@ -27,6 +27,7 @@ import cloud.commandframework.ArgumentDescription; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.parser.ArgumentParser; +import cloud.commandframework.bukkit.BukkitCommandContextKeys; import cloud.commandframework.bukkit.CloudBukkitCapabilities; import cloud.commandframework.bukkit.arguments.selector.SinglePlayerSelector; import cloud.commandframework.bukkit.parsers.PlayerArgument; @@ -34,6 +35,7 @@ import cloud.commandframework.context.CommandContext; import cloud.commandframework.exceptions.parsing.NoInputProvidedException; import com.google.common.collect.ImmutableList; import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.checkerframework.checker.nullness.qual.NonNull; @@ -42,7 +44,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Queue; -import java.util.Set; import java.util.function.BiFunction; public final class SinglePlayerSelectorArgument extends CommandArgument { @@ -152,7 +153,7 @@ public final class SinglePlayerSelectorArgument extends CommandArgument>get("CloudBukkitCapabilities").contains( + if (!commandContext.get(BukkitCommandContextKeys.CLOUD_BUKKIT_CAPABILITIES).contains( CloudBukkitCapabilities.BRIGADIER)) { @SuppressWarnings("deprecation") Player player = Bukkit.getPlayer(input); @@ -165,7 +166,7 @@ public final class SinglePlayerSelectorArgument extends CommandArgument entities; try { - entities = Bukkit.selectEntities(commandContext.get("BukkitCommandSender"), input); + entities = Bukkit.selectEntities(commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER), input); } catch (IllegalArgumentException e) { return ArgumentParseResult.failure(new SelectorParseException( input, @@ -205,6 +206,10 @@ public final class SinglePlayerSelectorArgument extends CommandArgument output = new ArrayList<>(); for (Player player : Bukkit.getOnlinePlayers()) { + final CommandSender bukkit = commandContext.get(BukkitCommandContextKeys.BUKKIT_COMMAND_SENDER); + if (bukkit instanceof Player && !((Player) bukkit).canSee(player)) { + continue; + } output.add(player.getName()); } diff --git a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java index 582780a7..8dafda73 100644 --- a/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java +++ b/cloud-minecraft/cloud-paper/src/main/java/cloud/commandframework/paper/PaperBrigadierListener.java @@ -27,15 +27,18 @@ import cloud.commandframework.CommandTree; import cloud.commandframework.arguments.CommandArgument; import cloud.commandframework.brigadier.CloudBrigadierManager; import cloud.commandframework.bukkit.BukkitBrigadierMapper; +import cloud.commandframework.bukkit.internal.CraftBukkitReflection; import cloud.commandframework.context.CommandContext; import cloud.commandframework.permission.CommandPermission; import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.checkerframework.checker.nullness.qual.NonNull; +import java.lang.reflect.Method; import java.util.function.BiPredicate; import java.util.regex.Pattern; @@ -46,18 +49,30 @@ class PaperBrigadierListener implements Listener { PaperBrigadierListener(final @NonNull PaperCommandManager paperCommandManager) { this.paperCommandManager = paperCommandManager; - this.brigadierManager = new CloudBrigadierManager<>( - this.paperCommandManager, - () -> new CommandContext<>( - this.paperCommandManager.getCommandSenderMapper() - .apply(Bukkit.getConsoleSender()), - this.paperCommandManager - ) - ); - this.brigadierManager.brigadierSenderMapper( - sender -> this.paperCommandManager.getCommandSenderMapper().apply(sender.getBukkitSender()) - ); + this.brigadierManager = new CloudBrigadierManager<>(this.paperCommandManager, () -> new CommandContext<>( + this.paperCommandManager.getCommandSenderMapper().apply(Bukkit.getConsoleSender()), + this.paperCommandManager + )); + + this.brigadierManager.brigadierSenderMapper(sender -> + this.paperCommandManager.getCommandSenderMapper().apply(sender.getBukkitSender())); + new BukkitBrigadierMapper<>(this.paperCommandManager, this.brigadierManager); + + if (!CraftBukkitReflection.craftBukkit()) { + return; + } + final Class vanillaCommandWrapperClass = CraftBukkitReflection.needOBCClass("command.VanillaCommandWrapper"); + final Method getListenerMethod = CraftBukkitReflection.needMethod( + vanillaCommandWrapperClass, "getListener", CommandSender.class); + this.brigadierManager.backwardsBrigadierSenderMapper(cloud -> { + try { + return (BukkitBrigadierCommandSource) getListenerMethod + .invoke(null, this.paperCommandManager.getBackwardsCommandSenderMapper().apply(cloud)); + } catch (final ReflectiveOperationException e) { + throw new RuntimeException(e); + } + }); } protected @NonNull CloudBrigadierManager brigadierManager() {