bukkit/paper: Update reflection for Minecraft 1.19 (#374)

This commit is contained in:
Jason 2022-06-09 01:52:32 -07:00
parent 2572b73c4b
commit 1fe1b4a0d3
8 changed files with 387 additions and 137 deletions

View file

@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Annotations: `@CommandMethod` annotation processing for compile-time validation ([#365](https://github.com/Incendo/cloud/pull/365))
- Add root command deletion support (core/pircbotx/javacord/jda/bukkit/paper) ([#369](https://github.com/Incendo/cloud/pull/369),
[#371](https://github.com/Incendo/cloud/pull/371))
- Bukkit/Paper: Full support for Minecraft 1.19
### Fixed
- Core: Fix missing caption registration for the regex caption ([#351](https://github.com/Incendo/cloud/pull/351))

View file

@ -26,6 +26,7 @@ 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.internal.CommandBuildContextSupplier;
import cloud.commandframework.bukkit.internal.MinecraftArgumentTypes;
import cloud.commandframework.bukkit.parsers.BlockPredicateArgument;
import cloud.commandframework.bukkit.parsers.EnchantmentArgument;
@ -88,12 +89,12 @@ public final class BukkitBrigadierMapper<C> {
this.mapSimpleNMS(new TypeToken<EnchantmentArgument.EnchantmentParser<C>>() {
}, "item_enchantment");
/* Map Item arguments */
this.mapSimpleNMS(new TypeToken<ItemStackArgument.Parser<C>>() {
this.mapSimpleContextNMS(new TypeToken<ItemStackArgument.Parser<C>>() {
}, "item_stack");
this.mapSimpleNMS(new TypeToken<ItemStackPredicateArgument.Parser<C>>() {
this.mapSimpleContextNMS(new TypeToken<ItemStackPredicateArgument.Parser<C>>() {
}, "item_predicate");
/* Map Block arguments */
this.mapSimpleNMS(new TypeToken<BlockPredicateArgument.Parser<C>>() {
this.mapSimpleContextNMS(new TypeToken<BlockPredicateArgument.Parser<C>>() {
}, "block_predicate");
/* Map Entity Selectors */
this.mapNMS(new TypeToken<SingleEntitySelectorArgument.SingleEntitySelectorParser<C>>() {
@ -156,13 +157,37 @@ public final class BukkitBrigadierMapper<C> {
}
}
/**
* Attempt to register a mapping between a cloud argument parser type and an NMS brigadier argument type which
* has a single-arg constructor taking CommandBuildContext.
*
* @param type Type to map
* @param <T> argument parser type
* @param argumentId registry id of argument type
* @since 1.7.0
*/
public <T extends ArgumentParser<C, ?>> void mapSimpleContextNMS(
final @NonNull TypeToken<T> type,
final @NonNull String argumentId
) {
this.mapNMS(type, () -> {
try {
return (ArgumentType<?>) MinecraftArgumentTypes.getClassByKey(NamespacedKey.minecraft(argumentId))
.getDeclaredConstructors()[0]
.newInstance(CommandBuildContextSupplier.commandBuildContext());
} catch (final ReflectiveOperationException e) {
throw new RuntimeException(e);
}
});
}
/**
* 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 <T> argument parser type
* @param argumentId network id of argument type
* @param argumentId registry id of argument type
* @since 1.5.0
*/
public <T extends ArgumentParser<C, ?>> void mapSimpleNMS(
@ -178,7 +203,7 @@ public final class BukkitBrigadierMapper<C> {
*
* @param type Type to map
* @param <T> argument parser type
* @param argumentId network id of argument type
* @param argumentId registry id of argument type
* @param useCloudSuggestions whether to use cloud suggestions
* @since 1.6.0
*/

View file

@ -0,0 +1,69 @@
//
// 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 java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* This is not API, and as such, may break, change, or be removed without any notice.
*/
@Beta
public final class CommandBuildContextSupplier {
private static final Class<?> COMMAND_BUILD_CONTEXT_CLASS = CraftBukkitReflection.needMCClass("commands.CommandBuildContext");
private static final Constructor<?> COMMAND_BUILD_CONTEXT_CTR = COMMAND_BUILD_CONTEXT_CLASS.getDeclaredConstructors()[0];
private static final Class<?> REG_ACC_CLASS = COMMAND_BUILD_CONTEXT_CTR.getParameterTypes()[0];
private static final Class<?> MC_SERVER_CLASS = CraftBukkitReflection.needNMSClassOrElse(
"MinecraftServer", "net.minecraft.server.MinecraftServer"
);
private static final Method GET_SERVER_METHOD;
private static final Method REGISTRY_ACCESS = Arrays.stream(MC_SERVER_CLASS.getDeclaredMethods())
.filter(m -> REG_ACC_CLASS.isAssignableFrom(m.getReturnType()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Cannot find MinecraftServer#registryAccess"));
static {
try {
GET_SERVER_METHOD = MC_SERVER_CLASS.getDeclaredMethod("getServer");
} catch (final NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private CommandBuildContextSupplier() {
}
public static Object commandBuildContext() {
try {
final Object server = GET_SERVER_METHOD.invoke(null);
return COMMAND_BUILD_CONTEXT_CTR.newInstance(REGISTRY_ACCESS.invoke(server));
} catch (final ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -62,8 +62,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
/**
* A registry of the {@link ArgumentType}s provided by Minecraft.
* <p>
* This file is taken from MIT licensed code in commodore (https://github.com/lucko/commodore).
*
* <p>This is not API, and as such, may break, change, or be removed without any notice.</p>
*/
@ -73,6 +71,64 @@ public final class MinecraftArgumentTypes {
private MinecraftArgumentTypes() {
}
private static final ArgumentTypeGetter ARGUMENT_TYPE_GETTER;
static {
if (CraftBukkitReflection.classExists("org.bukkit.entity.Warden")) {
ARGUMENT_TYPE_GETTER = new ArgumentTypeGetterImpl(); // 1.19+
} else {
ARGUMENT_TYPE_GETTER = new LegacyArgumentTypeGetter(); // 1.13-1.18.2
}
}
/**
* Gets a registered argument type class by key.
*
* @param key the key
* @return the returned argument type class
* @throws IllegalArgumentException if no such argument is registered
*/
public static Class<? extends ArgumentType<?>> getClassByKey(
final @NonNull NamespacedKey key
) throws IllegalArgumentException {
return ARGUMENT_TYPE_GETTER.getClassByKey(key);
}
private interface ArgumentTypeGetter {
Class<? extends ArgumentType<?>> getClassByKey(@NonNull NamespacedKey key) throws IllegalArgumentException;
}
@SuppressWarnings("unchecked")
private static final class ArgumentTypeGetterImpl implements MinecraftArgumentTypes.ArgumentTypeGetter {
private final Object argumentRegistry;
private final Map<?, ?> byClassMap;
private ArgumentTypeGetterImpl() {
this.argumentRegistry = RegistryReflection.registryByName("command_argument_type");
try {
final Field declaredField = CraftBukkitReflection.needMCClass("commands.synchronization.ArgumentTypeInfos")
.getDeclaredFields()[0];
declaredField.setAccessible(true);
this.byClassMap = (Map<?, ?>) declaredField.get(null);
} catch (final ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public Class<? extends ArgumentType<?>> getClassByKey(final @NonNull NamespacedKey key) throws IllegalArgumentException {
final Object argTypeInfo = RegistryReflection.get(this.argumentRegistry, key.getNamespace() + ":" + key.getKey());
for (final Map.Entry<?, ?> entry : this.byClassMap.entrySet()) {
if (entry.getValue() == argTypeInfo) {
return (Class<? extends ArgumentType<?>>) entry.getKey();
}
}
throw new IllegalArgumentException(key.toString());
}
}
@SuppressWarnings("unchecked")
private static final class LegacyArgumentTypeGetter implements ArgumentTypeGetter {
private static final Constructor<?> MINECRAFT_KEY_CONSTRUCTOR;
private static final Method ARGUMENT_REGISTRY_GET_BY_KEY_METHOD;
private static final Field BY_CLASS_MAP_FIELD;
@ -119,22 +175,13 @@ public final class MinecraftArgumentTypes {
.findFirst()
.orElseThrow(NoSuchFieldException::new);
BY_CLASS_MAP_FIELD.setAccessible(true);
} catch (ReflectiveOperationException e) {
} catch (final ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* Gets a registered argument type class by key.
*
* @param key the key
* @return the returned argument type class
* @throws IllegalArgumentException if no such argument is registered
*/
@SuppressWarnings("unchecked")
public static Class<? extends ArgumentType<?>> getClassByKey(
final @NonNull NamespacedKey key
) throws IllegalArgumentException {
@Override
public Class<? extends ArgumentType<?>> getClassByKey(final @NonNull NamespacedKey key) throws IllegalArgumentException {
try {
Object minecraftKey = MINECRAFT_KEY_CONSTRUCTOR.newInstance(key.getNamespace(), key.getKey());
Object entry = ARGUMENT_REGISTRY_GET_BY_KEY_METHOD.invoke(null, minecraftKey);
@ -153,5 +200,6 @@ public final class MinecraftArgumentTypes {
throw new RuntimeException(e);
}
}
}
}

View file

@ -0,0 +1,111 @@
//
// 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 java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* This is not API, and as such, may break, change, or be removed without any notice.
*/
@Beta
public final class RegistryReflection {
public static final @Nullable Field REGISTRY_REGISTRY;
public static final @Nullable Method REGISTRY_GET;
private static final Class<?> RESOURCE_LOCATION_CLASS = CraftBukkitReflection.needNMSClassOrElse(
"MinecraftKey",
"net.minecraft.resources.MinecraftKey",
"net.minecraft.resources.ResourceLocation"
);
private static final Constructor<?> RESOURCE_LOCATION_CTR = CraftBukkitReflection.needConstructor(
RESOURCE_LOCATION_CLASS,
String.class
);
private RegistryReflection() {
}
static {
Class<?> registryClass;
if (CraftBukkitReflection.MAJOR_REVISION < 17) {
REGISTRY_REGISTRY = null;
REGISTRY_GET = null;
} else {
registryClass = CraftBukkitReflection.firstNonNullOrThrow(
() -> "Registry",
CraftBukkitReflection.findMCClass("core.IRegistry"),
CraftBukkitReflection.findMCClass("core.Registry")
);
final Class<?> registryClassFinal = registryClass;
REGISTRY_REGISTRY = Arrays.stream(registryClass.getDeclaredFields())
.filter(it -> it.getType().equals(registryClassFinal))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find Registry Registry field"));
REGISTRY_REGISTRY.setAccessible(true);
final Class<?> resourceLocationClass = CraftBukkitReflection.firstNonNullOrThrow(
() -> "ResourceLocation class",
CraftBukkitReflection.findMCClass("resources.ResourceLocation"),
CraftBukkitReflection.findMCClass("resources.MinecraftKey")
);
REGISTRY_GET = Arrays.stream(registryClass.getDeclaredMethods())
.filter(it -> it.getParameterCount() == 1
&& it.getParameterTypes()[0].equals(resourceLocationClass)
&& it.getReturnType().equals(Object.class))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find Registry#get(ResourceLocation)"));
}
}
public static Object get(final Object registry, final String resourceLocation) {
Objects.requireNonNull(REGISTRY_GET, "REGISTRY_GET");
try {
return REGISTRY_GET.invoke(registry, RegistryReflection.createResourceLocation(resourceLocation));
} catch (final ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Object registryByName(final String name) {
Objects.requireNonNull(REGISTRY_REGISTRY, "REGISTRY_REGISTRY");
try {
return get(REGISTRY_REGISTRY.get(null), name);
} catch (final ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Object createResourceLocation(final String str) {
try {
return RESOURCE_LOCATION_CTR.newInstance(str);
} catch (final ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -30,15 +30,15 @@ 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.CommandBuildContextSupplier;
import cloud.commandframework.bukkit.internal.CraftBukkitReflection;
import cloud.commandframework.bukkit.internal.MinecraftArgumentTypes;
import cloud.commandframework.bukkit.internal.RegistryReflection;
import cloud.commandframework.context.CommandContext;
import com.mojang.brigadier.arguments.ArgumentType;
import io.leangen.geantyref.TypeToken;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
@ -144,60 +144,19 @@ public final class BlockPredicateArgument<C> extends CommandArgument<C, BlockPre
public static final class Parser<C> implements ArgumentParser<C, BlockPredicate> {
private static final Class<?> TAG_CONTAINER_CLASS;
private static final @Nullable Field REGISTRY_REGISTRY;
private static final @Nullable Method REGISTRY_GET;
private static final @Nullable Object BLOCK_REGISTRY_RESOURCE_LOCATION;
static {
Class<?> tagContainerClass;
if (CraftBukkitReflection.MAJOR_REVISION > 12 && CraftBukkitReflection.MAJOR_REVISION < 16) {
tagContainerClass = CraftBukkitReflection.needNMSClass("TagRegistry");
REGISTRY_REGISTRY = null;
REGISTRY_GET = null;
BLOCK_REGISTRY_RESOURCE_LOCATION = null;
} else {
tagContainerClass = CraftBukkitReflection.firstNonNullOrNull(
tagContainerClass = CraftBukkitReflection.firstNonNullOrThrow(
() -> "tagContainerClass",
CraftBukkitReflection.findNMSClass("ITagRegistry"),
CraftBukkitReflection.findMCClass("tags.ITagRegistry"),
CraftBukkitReflection.findMCClass("tags.TagContainer")
CraftBukkitReflection.findMCClass("tags.TagContainer"),
String.class // fail
);
if (tagContainerClass == null) {
tagContainerClass = CraftBukkitReflection.firstNonNullOrThrow(
() -> "Registry",
CraftBukkitReflection.findMCClass("core.IRegistry"),
CraftBukkitReflection.findMCClass("core.Registry")
);
final Class<?> tagContainerClassFinal = tagContainerClass;
REGISTRY_REGISTRY = Arrays.stream(tagContainerClass.getDeclaredFields())
.filter(it -> it.getType().equals(tagContainerClassFinal))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find Registry Registry field"));
REGISTRY_REGISTRY.setAccessible(true);
final Class<?> resourceLocationClass = CraftBukkitReflection.firstNonNullOrThrow(
() -> "ResourceLocation class",
CraftBukkitReflection.findMCClass("resources.ResourceLocation"),
CraftBukkitReflection.findMCClass("resources.MinecraftKey")
);
REGISTRY_GET = Arrays.stream(tagContainerClass.getDeclaredMethods())
.filter(it -> it.getParameterCount() == 1
&& it.getParameterTypes()[0].equals(resourceLocationClass)
&& it.getReturnType().equals(Object.class))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Could not find Registry#get(ResourceLocation)"));
final Constructor<?> resourceLocationCtr = CraftBukkitReflection.needConstructor(
resourceLocationClass,
String.class
);
try {
BLOCK_REGISTRY_RESOURCE_LOCATION = resourceLocationCtr.newInstance("block");
} catch (final ReflectiveOperationException e) {
throw new RuntimeException(e);
}
} else {
REGISTRY_REGISTRY = null;
REGISTRY_GET = null;
BLOCK_REGISTRY_RESOURCE_LOCATION = null;
}
}
TAG_CONTAINER_CLASS = tagContainerClass;
}
@ -244,8 +203,7 @@ public final class BlockPredicateArgument<C> extends CommandArgument<C, BlockPre
private static final Constructor<?> SHAPE_DETECTOR_BLOCK_CTR = CraftBukkitReflection
.needConstructor(SHAPE_DETECTOR_BLOCK_CLASS, LEVEL_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.firstNonNullOrThrow(
() -> "create on BlockPredicateArgument$Result",
private static final @Nullable Method CREATE_PREDICATE_METHOD = CraftBukkitReflection.firstNonNullOrNull(
CraftBukkitReflection.findMethod(ARGUMENT_BLOCK_PREDICATE_RESULT_CLASS, "create", TAG_CONTAINER_CLASS),
CraftBukkitReflection.findMethod(ARGUMENT_BLOCK_PREDICATE_RESULT_CLASS, "a", TAG_CONTAINER_CLASS)
);
@ -279,22 +237,30 @@ public final class BlockPredicateArgument<C> extends CommandArgument<C, BlockPre
@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 Constructor<?> ctr = ARGUMENT_BLOCK_PREDICATE_CLASS.getDeclaredConstructors()[0];
final ArgumentType<Object> inst;
if (ctr.getParameterCount() == 0) {
inst = (ArgumentType<Object>) ctr.newInstance();
} else {
// 1.19+
inst = (ArgumentType<Object>) ctr.newInstance(CommandBuildContextSupplier.commandBuildContext());
}
return new WrappedBrigadierParser<C, Object>(inst).map((ctx, result) -> {
if (result instanceof Predicate) {
// 1.19+
return ArgumentParseResult.success(new BlockPredicateImpl((Predicate<Object>) result));
}
final Object commandSourceStack = ctx.get(WrappedBrigadierParser.COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER);
try {
final Object server = GET_SERVER_METHOD.invoke(commandSourceStack);
final Object tagRegistry;
final Object obj;
if (GET_TAG_REGISTRY_METHOD != null) {
tagRegistry = GET_TAG_REGISTRY_METHOD.invoke(server);
obj = GET_TAG_REGISTRY_METHOD.invoke(server);
} else {
Objects.requireNonNull(REGISTRY_GET, "REGISTRY_GET");
Objects.requireNonNull(REGISTRY_REGISTRY, "REGISTRY_REGISTRY");
final Object registryRegistry = REGISTRY_REGISTRY.get(null);
tagRegistry = REGISTRY_GET.invoke(registryRegistry, BLOCK_REGISTRY_RESOURCE_LOCATION);
obj = RegistryReflection.registryByName("block");
}
final Predicate<Object> predicate = (Predicate<Object>) CREATE_PREDICATE_METHOD.invoke(result, tagRegistry);
Objects.requireNonNull(CREATE_PREDICATE_METHOD, "create on BlockPredicateArgument$Result");
final Predicate<Object> predicate = (Predicate<Object>) CREATE_PREDICATE_METHOD.invoke(result, obj);
return ArgumentParseResult.success(new BlockPredicateImpl(predicate));
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);

View file

@ -30,11 +30,13 @@ 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.CommandBuildContextSupplier;
import cloud.commandframework.bukkit.internal.CraftBukkitReflection;
import cloud.commandframework.bukkit.internal.MinecraftArgumentTypes;
import cloud.commandframework.context.CommandContext;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@ -179,7 +181,7 @@ public final class ItemStackArgument<C> extends CommandArgument<C, ProtoItemStac
}
private static @Nullable Class<?> findItemInputClass() {
final Class<?>[] classes = new Class<?>[] {
final Class<?>[] classes = new Class<?>[]{
CraftBukkitReflection.findNMSClass("ArgumentPredicateItemStack"),
CraftBukkitReflection.findMCClass("commands.arguments.item.ArgumentPredicateItemStack"),
CraftBukkitReflection.findMCClass("commands.arguments.item.ItemInput")
@ -228,6 +230,10 @@ public final class ItemStackArgument<C> extends CommandArgument<C, ProtoItemStac
CraftBukkitReflection.findField(ITEM_INPUT_CLASS, "c"),
CraftBukkitReflection.findField(ITEM_INPUT_CLASS, "tag")
);
private static final Class<?> HOLDER_CLASS = CraftBukkitReflection.findMCClass("core.Holder");
private static final @Nullable Method VALUE_METHOD = HOLDER_CLASS == null
? null
: CraftBukkitReflection.needMethod(HOLDER_CLASS, "value");
private final ArgumentParser<C, ProtoItemStack> parser;
@ -241,9 +247,16 @@ public final class ItemStackArgument<C> extends CommandArgument<C, ProtoItemStac
@SuppressWarnings("unchecked")
private ArgumentParser<C, ProtoItemStack> createParser() throws ReflectiveOperationException {
return new WrappedBrigadierParser<C, Object>(
(ArgumentType<Object>) ARGUMENT_ITEM_STACK_CLASS.getConstructor().newInstance()
).map((ctx, itemInput) -> ArgumentParseResult.success(new ModernProtoItemStack(itemInput)));
final Constructor<?> ctr = ARGUMENT_ITEM_STACK_CLASS.getDeclaredConstructors()[0];
final ArgumentType<Object> inst;
if (ctr.getParameterCount() == 0) {
inst = (ArgumentType<Object>) ctr.newInstance();
} else {
// 1.19+
inst = (ArgumentType<Object>) ctr.newInstance(CommandBuildContextSupplier.commandBuildContext());
}
return new WrappedBrigadierParser<C, Object>(inst)
.map((ctx, itemInput) -> ArgumentParseResult.success(new ModernProtoItemStack(itemInput)));
}
@Override
@ -272,7 +285,11 @@ public final class ItemStackArgument<C> extends CommandArgument<C, ProtoItemStac
ModernProtoItemStack(final @NonNull Object itemInput) {
this.itemInput = itemInput;
try {
this.material = (Material) GET_MATERIAL_METHOD.invoke(null, ITEM_FIELD.get(itemInput));
Object item = ITEM_FIELD.get(itemInput);
if (HOLDER_CLASS != null && HOLDER_CLASS.isInstance(item)) {
item = VALUE_METHOD.invoke(item);
}
this.material = (Material) GET_MATERIAL_METHOD.invoke(null, item);
final Object compoundTag = COMPOUND_TAG_FIELD.get(itemInput);
if (compoundTag != null) {
this.snbt = compoundTag.toString();

View file

@ -30,15 +30,18 @@ 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.CommandBuildContextSupplier;
import cloud.commandframework.bukkit.internal.CraftBukkitReflection;
import cloud.commandframework.bukkit.internal.MinecraftArgumentTypes;
import cloud.commandframework.context.CommandContext;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.StringRange;
import io.leangen.geantyref.TypeToken;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.function.BiFunction;
import java.util.function.Predicate;
@ -151,8 +154,7 @@ public final class ItemStackPredicateArgument<C> extends CommandArgument<C, Item
CraftBukkitReflection.findMCClass("commands.arguments.item.ArgumentItemPredicate$b"),
CraftBukkitReflection.findMCClass("commands.arguments.item.ItemPredicateArgument$Result")
);
private static final Method CREATE_PREDICATE_METHOD = CraftBukkitReflection.firstNonNullOrThrow(
() -> "ItemPredicateArgument$Result#create",
private static final @Nullable Method CREATE_PREDICATE_METHOD = CraftBukkitReflection.firstNonNullOrNull(
CraftBukkitReflection.findMethod(
ARGUMENT_ITEM_PREDICATE_RESULT_CLASS,
"create",
@ -184,11 +186,22 @@ public final class ItemStackPredicateArgument<C> extends CommandArgument<C, Item
@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 Constructor<?> ctr = ARGUMENT_ITEM_PREDICATE_CLASS.getDeclaredConstructors()[0];
final ArgumentType<Object> inst;
if (ctr.getParameterCount() == 0) {
inst = (ArgumentType<Object>) ctr.newInstance();
} else {
// 1.19+
inst = (ArgumentType<Object>) ctr.newInstance(CommandBuildContextSupplier.commandBuildContext());
}
return new WrappedBrigadierParser<C, Object>(inst).map((ctx, result) -> {
if (result instanceof Predicate) {
// 1.19+
return ArgumentParseResult.success(new ItemStackPredicateImpl((Predicate<Object>) result));
}
final Object commandSourceStack = ctx.get(WrappedBrigadierParser.COMMAND_CONTEXT_BRIGADIER_NATIVE_SENDER);
final com.mojang.brigadier.context.CommandContext<Object> dummy = createDummyContext(ctx, commandSourceStack);
Objects.requireNonNull(CREATE_PREDICATE_METHOD, "ItemPredicateArgument$Result#create");
try {
final Predicate<Object> predicate = (Predicate<Object>) CREATE_PREDICATE_METHOD.invoke(result, dummy);
return ArgumentParseResult.success(new ItemStackPredicateImpl(predicate));