bukkit: Implement ItemStack and Block predicate arguments (#259)

This commit is contained in:
Jason 2021-05-03 20:52:39 -07:00 committed by Jason
parent 0f1d05ef9c
commit 7da05da323
11 changed files with 748 additions and 32 deletions

View file

@ -9,6 +9,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))
- Bukkit: Implement parsers for ItemStackPredicate and BlockPredicate ([#259](https://github.com/Incendo/cloud/pull/259))
### Changed
- Use Command instead of TabCompleteEvent on Bukkit

View file

@ -27,8 +27,10 @@ import cloud.commandframework.arguments.parser.ArgumentParser;
import cloud.commandframework.arguments.standard.UUIDArgument;
import cloud.commandframework.brigadier.CloudBrigadierManager;
import cloud.commandframework.bukkit.internal.CraftBukkitReflection;
import cloud.commandframework.bukkit.parsers.BlockPredicateArgument;
import cloud.commandframework.bukkit.parsers.EnchantmentArgument;
import cloud.commandframework.bukkit.parsers.ItemStackArgument;
import cloud.commandframework.bukkit.parsers.ItemStackPredicateArgument;
import cloud.commandframework.bukkit.parsers.location.Location2DArgument;
import cloud.commandframework.bukkit.parsers.location.LocationArgument;
import cloud.commandframework.bukkit.parsers.selector.MultipleEntitySelectorArgument;
@ -90,9 +92,14 @@ public final class BukkitBrigadierMapper<C> {
/* Map Enchantment */
this.mapSimpleNMS(new TypeToken<EnchantmentArgument.EnchantmentParser<C>>() {
}, "Enchantment");
/* Map ItemStackArgument */
/* Map Item arguments */
this.mapSimpleNMS(new TypeToken<ItemStackArgument.Parser<C>>() {
}, "ItemStack");
this.mapSimpleNMS(new TypeToken<ItemStackPredicateArgument.Parser<C>>() {
}, "ItemPredicate");
/* Map Block arguments */
this.mapSimpleNMS(new TypeToken<BlockPredicateArgument.Parser<C>>() {
}, "BlockPredicate");
/* Map Entity Selectors */
this.mapNMS(new TypeToken<SingleEntitySelectorArgument.SingleEntitySelectorParser<C>>() {
}, this.entitySelectorArgumentSupplier(true, false));
@ -105,9 +112,9 @@ public final class BukkitBrigadierMapper<C> {
/* Map Vec3 */
this.mapNMS(new TypeToken<LocationArgument.LocationParser<C>>() {
}, this::argumentVec3);
/* Map Vec2I */
/* Map Vec2 */
this.mapNMS(new TypeToken<Location2DArgument.Location2DParser<C>>() {
}, this::argumentVec2i);
}, this::argumentVec2);
}
/**
@ -141,11 +148,11 @@ public final class BukkitBrigadierMapper<C> {
}
}
private @NonNull ArgumentType<?> argumentVec2i() {
private @NonNull ArgumentType<?> argumentVec2() {
try {
return (ArgumentType<?>) getNMSArgument("Vec2I").getDeclaredConstructor().newInstance();
return (ArgumentType<?>) getNMSArgument("Vec2").getDeclaredConstructor().newInstance();
} catch (final Exception e) {
this.commandManager.getOwningPlugin().getLogger().log(Level.INFO, "Failed to retrieve Vec2I argument", e);
this.commandManager.getOwningPlugin().getLogger().log(Level.INFO, "Failed to retrieve Vec2 argument", e);
return fallbackType();
}
}

View file

@ -33,8 +33,10 @@ 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.BlockPredicateArgument;
import cloud.commandframework.bukkit.parsers.EnchantmentArgument;
import cloud.commandframework.bukkit.parsers.ItemStackArgument;
import cloud.commandframework.bukkit.parsers.ItemStackPredicateArgument;
import cloud.commandframework.bukkit.parsers.MaterialArgument;
import cloud.commandframework.bukkit.parsers.OfflinePlayerArgument;
import cloud.commandframework.bukkit.parsers.PlayerArgument;
@ -62,6 +64,7 @@ import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.Set;
import java.util.function.Function;
@ -169,6 +172,12 @@ public class BukkitCommandManager<C> extends CommandManager<C> implements Brigad
this.getParserRegistry().registerParserSupplier(TypeToken.get(MultiplePlayerSelector.class), parserParameters ->
new MultiplePlayerSelectorArgument.MultiplePlayerSelectorParser<>());
/* Register MC 1.13+ parsers */
if (this.minecraftVersion >= BRIGADIER_MINIMUM_VERSION) {
this.registerParserSupplierFor(ItemStackPredicateArgument.class);
this.registerParserSupplierFor(BlockPredicateArgument.class);
}
/* Register suggestion and state listener */
this.owningPlugin.getServer().getPluginManager().registerEvents(
new CloudBukkitListener<>(this),
@ -396,6 +405,24 @@ public class BukkitCommandManager<C> extends CommandManager<C> implements Brigad
return this.backwardsCommandSenderMapper;
}
/**
* Attempts to call the method on the provided class matching the signature
* <p>{@code private static void registerParserSupplier(BukkitCommandManager)}</p>
* using reflection.
*
* @param argumentClass argument class
*/
private void registerParserSupplierFor(final @NonNull Class<?> argumentClass) {
try {
final Method registerParserSuppliers = argumentClass
.getDeclaredMethod("registerParserSupplier", BukkitCommandManager.class);
registerParserSuppliers.setAccessible(true);
registerParserSuppliers.invoke(null, this);
} catch (final ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
final void lockIfBrigadierCapable() {
if (this.minecraftVersion >= BRIGADIER_MINIMUM_VERSION) {
this.lockRegistration();

View file

@ -0,0 +1,55 @@
//
// 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.World;
import org.bukkit.block.Block;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.function.Predicate;
/**
* A {@link Predicate} for {@link Block Blocks} in a {@link World}, parsed from user input.
*
* <p>By default, a parsed {@link BlockPredicate} will not load chunks to perform tests. It will simply
* return {@code false} when attempting to test a block in unloaded chunks.</p>
*
* <p>To get a {@link BlockPredicate} which will load chunks, use {@link #loadChunks()}.</p>
*
* @since 1.5.0
*/
public interface BlockPredicate extends Predicate<Block> {
/**
* Get a version of this {@link BlockPredicate} which will load chunks in order to perform
* tests.
*
* <p>If this {@link BlockPredicate} already loads chunks, it will simply return itself.</p>
*
* @return a {@link BlockPredicate} which loads chunks
* @since 1.5.0
*/
@NonNull BlockPredicate loadChunks();
}

View file

@ -0,0 +1,37 @@
//
// 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.inventory.ItemStack;
import java.util.function.Predicate;
/**
* {@link Predicate} for {@link ItemStack ItemStacks}, parsed from user input.
*
* @since 1.5.0
*/
public interface ItemStackPredicate extends Predicate<ItemStack> {
}

View file

@ -28,6 +28,7 @@ import org.bukkit.Bukkit;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@ -99,15 +100,18 @@ public final class CraftBukkitReflection {
}
}
public static boolean classExists(final @NonNull String className) {
public static @NonNull Constructor<?> needConstructor(final @NonNull Class<?> holder, final @NonNull Class<?>... parameters) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
return holder.getDeclaredConstructor(parameters);
} catch (final NoSuchMethodException ex) {
throw new RuntimeException(ex);
}
}
public static boolean classExists(final @NonNull String className) {
return findClass(className) != null;
}
public static @NonNull Method needMethod(
final @NonNull Class<?> holder,
final @NonNull String name,

View file

@ -0,0 +1,281 @@
//
// 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.BlockPredicate;
import cloud.commandframework.bukkit.internal.CraftBukkitReflection;
import cloud.commandframework.context.CommandContext;
import com.mojang.brigadier.arguments.ArgumentType;
import io.leangen.geantyref.TypeToken;
import org.bukkit.block.Block;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Queue;
import java.util.function.BiFunction;
import java.util.function.Predicate;
/**
* Argument type for parsing a {@link BlockPredicate}.
*
* <p>This argument type is only usable on Minecraft 1.13+, as it depends on Minecraft internals added in that version.</p>
*
* <p>This argument type only provides basic suggestions by default. Enabling Brigadier compatibility through
* {@link BukkitCommandManager#registerBrigadier()} will allow client side validation and suggestions to be utilized.</p>
*
* @param <C> Command sender type
* @since 1.5.0
*/
public final class BlockPredicateArgument<C> extends CommandArgument<C, BlockPredicate> {
private BlockPredicateArgument(
final boolean required,
final @NonNull String name,
final @NonNull String defaultValue,
final @Nullable BiFunction<@NonNull CommandContext<C>, @NonNull String,
@NonNull List<@NonNull String>> suggestionsProvider,
final @NonNull ArgumentDescription defaultDescription
) {
super(required, name, new Parser<>(), defaultValue, BlockPredicate.class, suggestionsProvider, defaultDescription);
}
/**
* Create a new {@link Builder}.
*
* @param name Name of the argument
* @param <C> Command sender type
* @return Created builder
* @since 1.5.0
*/
public static <C> BlockPredicateArgument.@NonNull Builder<C> builder(final @NonNull String name) {
return new BlockPredicateArgument.Builder<>(name);
}
/**
* Create a new required {@link BlockPredicateArgument}.
*
* @param name Argument name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull BlockPredicateArgument<C> of(final @NonNull String name) {
return BlockPredicateArgument.<C>builder(name).build();
}
/**
* Create a new optional {@link BlockPredicateArgument}.
*
* @param name Argument name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull BlockPredicateArgument<C> optional(final @NonNull String name) {
return BlockPredicateArgument.<C>builder(name).asOptional().build();
}
/**
* Builder for {@link BlockPredicateArgument}.
*
* @param <C> sender type
* @since 1.5.0
*/
public static final class Builder<C> extends TypedBuilder<C, BlockPredicate, Builder<C>> {
private Builder(final @NonNull String name) {
super(BlockPredicate.class, name);
}
@Override
public @NonNull BlockPredicateArgument<C> build() {
return new BlockPredicateArgument<>(
this.isRequired(),
this.getName(),
this.getDefaultValue(),
this.getSuggestionsProvider(),
this.getDefaultDescription()
);
}
}
/**
* Parser for {@link BlockPredicateArgument}. Only supported on Minecraft 1.13 and newer CraftBukkit based servers.
*
* @param <C> sender type
* @since 1.5.0
*/
public static final class Parser<C> implements ArgumentParser<C, BlockPredicate> {
private static final Class<?> TAG_REGISTRY_CLASS;
static {
if (CraftBukkitReflection.MAJOR_REVISION >= 16) {
TAG_REGISTRY_CLASS = CraftBukkitReflection.needNMSClass("ITagRegistry");
} else {
TAG_REGISTRY_CLASS = CraftBukkitReflection.needNMSClass("TagRegistry");
}
}
private static final Class<?> CRAFT_WORLD_CLASS = CraftBukkitReflection.needOBCClass("CraftWorld");
private static final Class<?> MINECRAFT_SERVER_CLASS = CraftBukkitReflection.needNMSClass("MinecraftServer");
private static final Class<?> COMMAND_LISTENER_WRAPPER_CLASS =
CraftBukkitReflection.needNMSClass("CommandListenerWrapper");
private static final Class<?> ARGUMENT_BLOCK_PREDICATE_CLASS =
CraftBukkitReflection.needNMSClass("ArgumentBlockPredicate");
private static final Class<?> ARGUMENT_BLOCK_PREDICATE_RESULT_CLASS =
CraftBukkitReflection.needNMSClass("ArgumentBlockPredicate$b");
private static final Class<?> SHAPE_DETECTOR_BLOCK_CLASS = // BlockInWorld
CraftBukkitReflection.needNMSClass("ShapeDetectorBlock");
private static final Class<?> I_WORLD_READER_CLASS = CraftBukkitReflection.needNMSClass("IWorldReader");
private static final Class<?> BLOCK_POSITION_CLASS = CraftBukkitReflection.needNMSClass("BlockPosition");
private static final Constructor<?> BLOCK_POSITION_CTR =
CraftBukkitReflection.needConstructor(BLOCK_POSITION_CLASS, int.class, int.class, int.class);
private static final Constructor<?> SHAPE_DETECTOR_BLOCK_CTR = CraftBukkitReflection
.needConstructor(SHAPE_DETECTOR_BLOCK_CLASS, I_WORLD_READER_CLASS, BLOCK_POSITION_CLASS, boolean.class);
private static final Method GET_HANDLE_METHOD = CraftBukkitReflection.needMethod(CRAFT_WORLD_CLASS, "getHandle");
private static final Method CREATE_PREDICATE_METHOD =
CraftBukkitReflection.needMethod(ARGUMENT_BLOCK_PREDICATE_RESULT_CLASS, "create", TAG_REGISTRY_CLASS);
private static final Method GET_SERVER_METHOD =
CraftBukkitReflection.needMethod(COMMAND_LISTENER_WRAPPER_CLASS, "getServer");
private static final Method GET_TAG_REGISTRY_METHOD =
CraftBukkitReflection.needMethod(MINECRAFT_SERVER_CLASS, "getTagRegistry");
private final ArgumentParser<C, BlockPredicate> parser;
/**
* Create a new {@link Parser}.
*
* @since 1.5.0
*/
public Parser() {
try {
this.parser = this.createParser();
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException("Failed to initialize BlockPredicate parser.", ex);
}
}
@SuppressWarnings("unchecked")
private ArgumentParser<C, BlockPredicate> createParser() throws ReflectiveOperationException {
return new WrappedBrigadierParser<C, Object>(
(ArgumentType<Object>) ARGUMENT_BLOCK_PREDICATE_CLASS.getConstructor().newInstance()
).map((ctx, result) -> {
final Object commandSourceStack = ctx.get(WrappedBrigadierParser.COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER);
try {
final Object server = GET_SERVER_METHOD.invoke(commandSourceStack);
final Object tagRegistry = GET_TAG_REGISTRY_METHOD.invoke(server);
final Predicate<Object> predicate = (Predicate<Object>) CREATE_PREDICATE_METHOD.invoke(result, tagRegistry);
return ArgumentParseResult.success(new BlockPredicateImpl(predicate));
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
});
}
@Override
public @NonNull ArgumentParseResult<@NonNull BlockPredicate> parse(
@NonNull final CommandContext<@NonNull C> commandContext,
@NonNull final Queue<@NonNull String> inputQueue
) {
return this.parser.parse(commandContext, inputQueue);
}
@Override
public @NonNull List<@NonNull String> suggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
) {
return this.parser.suggestions(commandContext, input);
}
private static final class BlockPredicateImpl implements BlockPredicate {
private final Predicate<Object> predicate;
BlockPredicateImpl(final @NonNull Predicate<Object> predicate) {
this.predicate = predicate;
}
private boolean testImpl(final @NonNull Block block, final boolean loadChunks) {
try {
final Object blockInWorld = SHAPE_DETECTOR_BLOCK_CTR.newInstance(
GET_HANDLE_METHOD.invoke(block.getWorld()),
BLOCK_POSITION_CTR.newInstance(block.getX(), block.getY(), block.getZ()),
loadChunks
);
return this.predicate.test(blockInWorld);
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
@Override
public boolean test(final @NonNull Block block) {
return this.testImpl(block, false);
}
@Override
public @NonNull BlockPredicate loadChunks() {
return new BlockPredicate() {
@Override
public @NonNull BlockPredicate loadChunks() {
return this;
}
@Override
public boolean test(final Block block) {
return BlockPredicateImpl.this.testImpl(block, true);
}
};
}
}
}
/**
* Called reflectively by {@link BukkitCommandManager}.
*
* @param commandManager command manager
* @param <C> sender type
*/
@SuppressWarnings("unused")
private static <C> void registerParserSupplier(final @NonNull BukkitCommandManager<C> commandManager) {
commandManager.getParserRegistry()
.registerParserSupplier(TypeToken.get(BlockPredicate.class), params -> new BlockPredicateArgument.Parser<>());
}
}

View file

@ -92,7 +92,7 @@ public final class ItemStackArgument<C> extends CommandArgument<C, ProtoItemStac
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull CommandArgument<C, ProtoItemStack> of(final @NonNull String name) {
public static <C> @NonNull ItemStackArgument<C> of(final @NonNull String name) {
return ItemStackArgument.<C>builder(name).build();
}
@ -104,7 +104,7 @@ public final class ItemStackArgument<C> extends CommandArgument<C, ProtoItemStac
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull CommandArgument<C, ProtoItemStack> optional(final @NonNull String name) {
public static <C> @NonNull ItemStackArgument<C> optional(final @NonNull String name) {
return ItemStackArgument.<C>builder(name).asOptional().build();
}

View file

@ -0,0 +1,257 @@
//
// 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.ItemStackPredicate;
import cloud.commandframework.bukkit.internal.CraftBukkitReflection;
import cloud.commandframework.context.CommandContext;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.StringRange;
import io.leangen.geantyref.TypeToken;
import org.bukkit.inventory.ItemStack;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.function.BiFunction;
import java.util.function.Predicate;
/**
* Argument type for parsing an {@link ItemStackPredicate}.
*
* <p>This argument type is only usable on Minecraft 1.13+, as it depends on Minecraft internals added in that version.</p>
*
* <p>This argument type only provides basic suggestions by default. Enabling Brigadier compatibility through
* {@link BukkitCommandManager#registerBrigadier()} will allow client side validation and suggestions to be utilized.</p>
*
* @param <C> Command sender type
* @since 1.5.0
*/
public final class ItemStackPredicateArgument<C> extends CommandArgument<C, ItemStackPredicate> {
private ItemStackPredicateArgument(
final boolean required,
final @NonNull String name,
final @NonNull String defaultValue,
final @Nullable BiFunction<@NonNull CommandContext<C>, @NonNull String,
@NonNull List<@NonNull String>> suggestionsProvider,
final @NonNull ArgumentDescription defaultDescription
) {
super(required, name, new Parser<>(), defaultValue, ItemStackPredicate.class, suggestionsProvider, defaultDescription);
}
/**
* Create a new {@link Builder}.
*
* @param name Name of the argument
* @param <C> Command sender type
* @return Created builder
* @since 1.5.0
*/
public static <C> ItemStackPredicateArgument.@NonNull Builder<C> builder(final @NonNull String name) {
return new ItemStackPredicateArgument.Builder<>(name);
}
/**
* Create a new required {@link ItemStackPredicateArgument}.
*
* @param name Argument name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull ItemStackPredicateArgument<C> of(final @NonNull String name) {
return ItemStackPredicateArgument.<C>builder(name).build();
}
/**
* Create a new optional {@link ItemStackPredicateArgument}.
*
* @param name Argument name
* @param <C> Command sender type
* @return Created argument
* @since 1.5.0
*/
public static <C> @NonNull ItemStackPredicateArgument<C> optional(final @NonNull String name) {
return ItemStackPredicateArgument.<C>builder(name).asOptional().build();
}
/**
* Builder for {@link ItemStackPredicateArgument}.
*
* @param <C> sender type
* @since 1.5.0
*/
public static final class Builder<C> extends TypedBuilder<C, ItemStackPredicate, Builder<C>> {
private Builder(final @NonNull String name) {
super(ItemStackPredicate.class, name);
}
@Override
public @NonNull ItemStackPredicateArgument<C> build() {
return new ItemStackPredicateArgument<>(
this.isRequired(),
this.getName(),
this.getDefaultValue(),
this.getSuggestionsProvider(),
this.getDefaultDescription()
);
}
}
/**
* Parser for {@link ItemStackPredicateArgument}. Only supported on Minecraft 1.13 and newer CraftBukkit based servers.
*
* @param <C> sender type
* @since 1.5.0
*/
public static final class Parser<C> implements ArgumentParser<C, ItemStackPredicate> {
private static final Class<?> CRAFT_ITEM_STACK_CLASS =
CraftBukkitReflection.needOBCClass("inventory.CraftItemStack");
private static final Class<?> ARGUMENT_ITEM_PREDICATE_CLASS =
CraftBukkitReflection.needNMSClass("ArgumentItemPredicate");
private static final Class<?> ARGUMENT_ITEM_PREDICATE_RESULT_CLASS =
CraftBukkitReflection.needNMSClass("ArgumentItemPredicate$b");
private static final Method CREATE_PREDICATE_METHOD = CraftBukkitReflection.needMethod(
ARGUMENT_ITEM_PREDICATE_RESULT_CLASS,
"create",
com.mojang.brigadier.context.CommandContext.class
);
private static final Method AS_NMS_COPY_METHOD =
CraftBukkitReflection.needMethod(CRAFT_ITEM_STACK_CLASS, "asNMSCopy", ItemStack.class);
private final ArgumentParser<C, ItemStackPredicate> parser;
/**
* Create a new {@link Parser}.
*
* @since 1.5.0
*/
public Parser() {
try {
this.parser = this.createParser();
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException("Failed to initialize ItemPredicate parser.", ex);
}
}
@SuppressWarnings("unchecked")
private ArgumentParser<C, ItemStackPredicate> createParser() throws ReflectiveOperationException {
return new WrappedBrigadierParser<C, Object>(
(ArgumentType<Object>) ARGUMENT_ITEM_PREDICATE_CLASS.getConstructor().newInstance()
).map((ctx, result) -> {
final Object commandSourceStack = ctx.get(WrappedBrigadierParser.COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER);
final com.mojang.brigadier.context.CommandContext<Object> dummy = createDummyContext(ctx, commandSourceStack);
try {
final Predicate<Object> predicate = (Predicate<Object>) CREATE_PREDICATE_METHOD.invoke(result, dummy);
return ArgumentParseResult.success(new ItemStackPredicateImpl(predicate));
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
});
}
private static <C> com.mojang.brigadier.context.@NonNull CommandContext<Object> createDummyContext(
final @NonNull CommandContext<C> ctx,
final @NonNull Object commandSourceStack
) {
return new com.mojang.brigadier.context.CommandContext<>(
commandSourceStack,
ctx.getRawInputJoined(),
Collections.emptyMap(),
null,
null,
Collections.emptyList(),
StringRange.at(0),
null,
null,
false
);
}
@Override
public @NonNull ArgumentParseResult<@NonNull ItemStackPredicate> parse(
@NonNull final CommandContext<@NonNull C> commandContext,
@NonNull final Queue<@NonNull String> inputQueue
) {
return this.parser.parse(commandContext, inputQueue);
}
@Override
public @NonNull List<@NonNull String> suggestions(
final @NonNull CommandContext<C> commandContext,
final @NonNull String input
) {
return this.parser.suggestions(commandContext, input);
}
private static final class ItemStackPredicateImpl implements ItemStackPredicate {
private final Predicate<Object> predicate;
ItemStackPredicateImpl(final @NonNull Predicate<Object> predicate) {
this.predicate = predicate;
}
@Override
public boolean test(final @NonNull ItemStack itemStack) {
try {
return this.predicate.test(AS_NMS_COPY_METHOD.invoke(null, itemStack));
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
}
}
/**
* Called reflectively by {@link BukkitCommandManager}.
*
* @param commandManager command manager
* @param <C> sender type
*/
@SuppressWarnings("unused")
private static <C> void registerParserSupplier(final @NonNull BukkitCommandManager<C> commandManager) {
commandManager.getParserRegistry().registerParserSupplier(
TypeToken.get(ItemStackPredicate.class),
params -> new ItemStackPredicateArgument.Parser<>()
);
}
}

View file

@ -22,5 +22,5 @@ dependencies {
implementation("me.lucko", "commodore", Versions.commodore)
implementation("net.kyori", "adventure-platform-bukkit", Versions.adventurePlatform)
/* Bukkit */
compileOnly("org.bukkit", "bukkit", "1.8.8-R0.1-SNAPSHOT")
compileOnly("org.bukkit", "bukkit", Versions.bukkit)
}

View file

@ -26,12 +26,6 @@ package cloud.commandframework.examples.bukkit;
import cloud.commandframework.ArgumentDescription;
import cloud.commandframework.Command;
import cloud.commandframework.CommandTree;
import cloud.commandframework.arguments.compound.ArgumentPair;
import cloud.commandframework.bukkit.data.ProtoItemStack;
import cloud.commandframework.keys.SimpleCloudKey;
import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler;
import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys;
import cloud.commandframework.minecraft.extras.MinecraftHelp;
import cloud.commandframework.annotations.AnnotationParser;
import cloud.commandframework.annotations.Argument;
import cloud.commandframework.annotations.CommandDescription;
@ -50,7 +44,13 @@ import cloud.commandframework.arguments.standard.StringArrayArgument;
import cloud.commandframework.bukkit.BukkitCommandManager;
import cloud.commandframework.bukkit.CloudBukkitCapabilities;
import cloud.commandframework.bukkit.arguments.selector.SingleEntitySelector;
import cloud.commandframework.bukkit.data.BlockPredicate;
import cloud.commandframework.bukkit.data.ItemStackPredicate;
import cloud.commandframework.bukkit.data.ProtoItemStack;
import cloud.commandframework.bukkit.parsers.BlockPredicateArgument;
import cloud.commandframework.bukkit.parsers.EnchantmentArgument;
import cloud.commandframework.bukkit.parsers.ItemStackArgument;
import cloud.commandframework.bukkit.parsers.ItemStackPredicateArgument;
import cloud.commandframework.bukkit.parsers.MaterialArgument;
import cloud.commandframework.bukkit.parsers.WorldArgument;
import cloud.commandframework.bukkit.parsers.selector.SingleEntitySelectorArgument;
@ -59,7 +59,10 @@ import cloud.commandframework.captions.SimpleCaptionRegistry;
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.extra.confirmation.CommandConfirmationManager;
import cloud.commandframework.keys.SimpleCloudKey;
import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler;
import cloud.commandframework.minecraft.extras.MinecraftHelp;
import cloud.commandframework.minecraft.extras.RichDescription;
import cloud.commandframework.minecraft.extras.TextColorArgument;
import cloud.commandframework.paper.PaperCommandManager;
@ -80,6 +83,8 @@ import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.command.CommandSender;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType;
@ -343,7 +348,7 @@ public final class ExamplePlugin extends JavaPlugin {
.argument(IntegerArgument.of("level"))
.handler(c -> this.manager.taskRecipe().begin(c).synchronous(ctx -> {
final Player player = ((Player) ctx.getSender());
player.getInventory().getItemInHand().addEnchantment(ctx.get("enchant"), ctx.get("level"));
player.getInventory().getItemInMainHand().addEnchantment(ctx.get("enchant"), ctx.get("level"));
}).execute()));
//
@ -381,6 +386,47 @@ public final class ExamplePlugin extends JavaPlugin {
)))
);
// MC 1.13+ commands
if (this.manager.queryCapability(CloudBukkitCapabilities.BRIGADIER)) {
this.manager.command(builder.literal("replace")
.senderType(Player.class)
.argument(BlockPredicateArgument.of("predicate"))
.literal("with")
.argument(MaterialArgument.of("block")) // todo: use BlockDataArgument
.argument(IntegerArgument.<CommandSender>newBuilder("radius").withMin(1))
.handler(ctx -> {
final BlockData block = ctx.<Material>get("block").createBlockData();
final BlockPredicate predicate = ctx.get("predicate");
final int radius = ctx.get("radius");
final Player player = (Player) ctx.getSender();
final Location loc = player.getLocation();
this.manager.taskRecipe().begin(ctx).synchronous(context -> {
for (double x = loc.getX() - radius; x < loc.getX() + radius; x++) {
for (double y = loc.getY() - radius; y < loc.getY() + radius; y++) {
for (double z = loc.getZ() - radius; z < loc.getZ() + radius; z++) {
final Block blockAt = player.getWorld().getBlockAt((int) x, (int) y, (int) z);
if (predicate.test(blockAt)) {
blockAt.setBlockData(block);
}
}
}
}
}).execute();
}));
this.manager.command(builder.literal("test_item")
.argument(ItemStackArgument.of("item"))
.literal("is")
.argument(ItemStackPredicateArgument.of("predicate"))
.handler(ctx -> {
final ProtoItemStack protoItemStack = ctx.get("item");
final ItemStackPredicate predicate = ctx.get("predicate");
ctx.getSender().sendMessage("result: " + predicate.test(
protoItemStack.createItemStack(1, true)));
}));
}
//
// Create a Bukkit-like command
//
@ -415,20 +461,21 @@ public final class ExamplePlugin extends JavaPlugin {
);
}
// vanilla-like give command
this.manager.command(this.manager.commandBuilder("givetest")
// compound itemstack arg
this.manager.command(this.manager.commandBuilder("gib")
.senderType(Player.class)
.argument(ArgumentPair.of(
this.manager,
.argumentPair(
"itemstack",
TypeToken.get(ItemStack.class),
Pair.of("item", "amount"),
Pair.of(ProtoItemStack.class, Integer.class)
).withMapper(ItemStack.class, (sender, pair) -> {
final ProtoItemStack proto = pair.getFirst();
final int amount = pair.getSecond();
return proto.createItemStack(amount, true);
}), ArgumentDescription.of("The ItemStack to give"))
.meta(MinecraftExtrasMetaKeys.DESCRIPTION, text("Vanilla-like give command"))
Pair.of(ProtoItemStack.class, Integer.class),
(sender, pair) -> {
final ProtoItemStack proto = pair.getFirst();
final int amount = pair.getSecond();
return proto.createItemStack(amount, true);
},
ArgumentDescription.of("The ItemStack to give")
)
.handler(ctx -> ((Player) ctx.getSender()).getInventory().addItem(ctx.get("itemstack"))));
}