Skip to content

Commit

Permalink
bukkit/paper: Update reflection for Minecraft 1.19 (#374)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpenilla committed Jun 9, 2022
1 parent e60f040 commit 9dde077
Show file tree
Hide file tree
Showing 8 changed files with 386 additions and 136 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -88,12 +89,12 @@ private void registerMappings() {
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>>() {
Expand Down Expand Up @@ -156,13 +157,37 @@ private void registerMappings() {
}
}

/**
* 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(
Expand All @@ -176,9 +201,9 @@ private void registerMappings() {
* 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 type Type to map
* @param <T> argument parser type
* @param argumentId registry id of argument type
* @param useCloudSuggestions whether to use cloud suggestions
* @since 1.6.0
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@

/**
* 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>
*/
Expand All @@ -73,54 +71,13 @@ public final class MinecraftArgumentTypes {
private MinecraftArgumentTypes() {
}

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;
private static final ArgumentTypeGetter ARGUMENT_TYPE_GETTER;

static {
try {
final Class<?> minecraftKey;
final Class<?> argumentRegistry;

if (CraftBukkitReflection.findMCClass("resources.ResourceLocation") != null) {
minecraftKey = CraftBukkitReflection.needMCClass("resources.ResourceLocation");
argumentRegistry = CraftBukkitReflection.needMCClass("commands.synchronization.ArgumentTypes");
} else {
minecraftKey = CraftBukkitReflection.needNMSClassOrElse(
"MinecraftKey",
"net.minecraft.resources.MinecraftKey"
);
argumentRegistry = CraftBukkitReflection.needNMSClassOrElse(
"ArgumentRegistry",
"net.minecraft.commands.synchronization.ArgumentRegistry"
);
}

MINECRAFT_KEY_CONSTRUCTOR = minecraftKey.getConstructor(String.class, String.class);
MINECRAFT_KEY_CONSTRUCTOR.setAccessible(true);

ARGUMENT_REGISTRY_GET_BY_KEY_METHOD = Arrays.stream(argumentRegistry.getDeclaredMethods())
.filter(method -> method.getParameterCount() == 1)
.filter(method -> minecraftKey.equals(method.getParameterTypes()[0]))
.findFirst().orElseThrow(NoSuchMethodException::new);
ARGUMENT_REGISTRY_GET_BY_KEY_METHOD.setAccessible(true);

BY_CLASS_MAP_FIELD = Arrays.stream(argumentRegistry.getDeclaredFields())
.filter(field -> Modifier.isStatic(field.getModifiers()))
.filter(field -> field.getType().equals(Map.class))
.filter(field -> {
final ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
final Type param = parameterizedType.getActualTypeArguments()[0];
if (!(param instanceof ParameterizedType)) {
return false;
}
return ((ParameterizedType) param).getRawType().equals(Class.class);
})
.findFirst()
.orElseThrow(NoSuchFieldException::new);
BY_CLASS_MAP_FIELD.setAccessible(true);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
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
}
}

Expand All @@ -131,26 +88,117 @@ private MinecraftArgumentTypes() {
* @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 {
try {
Object minecraftKey = MINECRAFT_KEY_CONSTRUCTOR.newInstance(key.getNamespace(), key.getKey());
Object entry = ARGUMENT_REGISTRY_GET_BY_KEY_METHOD.invoke(null, minecraftKey);
if (entry == null) {
throw new IllegalArgumentException(key.toString());
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);
}
}

final Map<Class<?>, Object> map = (Map<Class<?>, Object>) BY_CLASS_MAP_FIELD.get(null);
for (final Map.Entry<Class<?>, Object> mapEntry : map.entrySet()) {
if (mapEntry.getValue() == entry) {
return (Class<? extends ArgumentType<?>>) mapEntry.getKey();
@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());
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}

@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;

static {
try {
final Class<?> minecraftKey;
final Class<?> argumentRegistry;

if (CraftBukkitReflection.findMCClass("resources.ResourceLocation") != null) {
minecraftKey = CraftBukkitReflection.needMCClass("resources.ResourceLocation");
argumentRegistry = CraftBukkitReflection.needMCClass("commands.synchronization.ArgumentTypes");
} else {
minecraftKey = CraftBukkitReflection.needNMSClassOrElse(
"MinecraftKey",
"net.minecraft.resources.MinecraftKey"
);
argumentRegistry = CraftBukkitReflection.needNMSClassOrElse(
"ArgumentRegistry",
"net.minecraft.commands.synchronization.ArgumentRegistry"
);
}

MINECRAFT_KEY_CONSTRUCTOR = minecraftKey.getConstructor(String.class, String.class);
MINECRAFT_KEY_CONSTRUCTOR.setAccessible(true);

ARGUMENT_REGISTRY_GET_BY_KEY_METHOD = Arrays.stream(argumentRegistry.getDeclaredMethods())
.filter(method -> method.getParameterCount() == 1)
.filter(method -> minecraftKey.equals(method.getParameterTypes()[0]))
.findFirst().orElseThrow(NoSuchMethodException::new);
ARGUMENT_REGISTRY_GET_BY_KEY_METHOD.setAccessible(true);

BY_CLASS_MAP_FIELD = Arrays.stream(argumentRegistry.getDeclaredFields())
.filter(field -> Modifier.isStatic(field.getModifiers()))
.filter(field -> field.getType().equals(Map.class))
.filter(field -> {
final ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
final Type param = parameterizedType.getActualTypeArguments()[0];
if (!(param instanceof ParameterizedType)) {
return false;
}
return ((ParameterizedType) param).getRawType().equals(Class.class);
})
.findFirst()
.orElseThrow(NoSuchFieldException::new);
BY_CLASS_MAP_FIELD.setAccessible(true);
} catch (final ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}

@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);
if (entry == null) {
throw new IllegalArgumentException(key.toString());
}

final Map<Class<?>, Object> map = (Map<Class<?>, Object>) BY_CLASS_MAP_FIELD.get(null);
for (final Map.Entry<Class<?>, Object> mapEntry : map.entrySet()) {
if (mapEntry.getValue() == entry) {
return (Class<? extends ArgumentType<?>>) mapEntry.getKey();
}
}
throw new IllegalArgumentException(key.toString());
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}

Expand Down
Loading

0 comments on commit 9dde077

Please sign in to comment.