mirror of
https://github.com/IntellectualSites/PlotSquared.git
synced 2025-10-21 05:33:45 +02:00
chore: split logic per platform
This commit is contained in:
@@ -52,6 +52,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class BukkitQueueCoordinator extends BasicQueueCoordinator {
|
||||
@@ -210,8 +211,13 @@ public class BukkitQueueCoordinator extends BasicQueueCoordinator {
|
||||
BaseBlock block = getWorld().getBlock(blockVector3).toBaseBlock(tag);
|
||||
getWorld().setBlock(blockVector3, block, getSideEffectSet(SideEffectState.NONE));
|
||||
} catch (WorldEditException ignored) {
|
||||
StateWrapper sw = new StateWrapper(tag);
|
||||
sw.restoreTag(getWorld().getName(), blockVector3.getX(), blockVector3.getY(), blockVector3.getZ());
|
||||
StateWrapper.INSTANCE.restore(
|
||||
getWorld().getName(),
|
||||
blockVector3.getX(),
|
||||
blockVector3.getY(),
|
||||
blockVector3.getZ(),
|
||||
tag
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -295,9 +301,7 @@ public class BukkitQueueCoordinator extends BasicQueueCoordinator {
|
||||
existing.setBlockData(blockData, false);
|
||||
if (block.hasNbtData()) {
|
||||
CompoundTag tag = block.getNbtData();
|
||||
StateWrapper sw = new StateWrapper(tag);
|
||||
|
||||
sw.restoreTag(existing);
|
||||
StateWrapper.INSTANCE.restore(existing, Objects.requireNonNull(tag));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -34,6 +34,8 @@ import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.generator.LimitedRegion;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Wraps a {@link LimitedRegion} inside a {@link com.plotsquared.core.queue.QueueCoordinator} so it can be written to.
|
||||
*
|
||||
@@ -44,7 +46,6 @@ public class LimitedRegionWrapperQueue extends DelegateQueueCoordinator {
|
||||
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + LimitedRegionWrapperQueue.class.getSimpleName());
|
||||
|
||||
private final LimitedRegion limitedRegion;
|
||||
private boolean useOtherRestoreTagMethod = false;
|
||||
|
||||
/**
|
||||
* @since 6.9.0
|
||||
@@ -64,20 +65,11 @@ public class LimitedRegionWrapperQueue extends DelegateQueueCoordinator {
|
||||
boolean result = setBlock(x, y, z, id.toImmutableState());
|
||||
if (result && id.hasNbtData()) {
|
||||
CompoundTag tag = id.getNbtData();
|
||||
StateWrapper sw = new StateWrapper(tag);
|
||||
try {
|
||||
if (useOtherRestoreTagMethod && getWorld() != null) {
|
||||
sw.restoreTag(getWorld().getName(), x, y, z);
|
||||
} else {
|
||||
sw.restoreTag(limitedRegion.getBlockState(x, y, z).getBlock());
|
||||
}
|
||||
StateWrapper.INSTANCE.restore(limitedRegion.getBlockState(x, y, z).getBlock(), Objects.requireNonNull(tag));
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.error("Error attempting to populate tile entity into the world at location {},{},{}", x, y, z, e);
|
||||
return false;
|
||||
} catch (IllegalStateException e) {
|
||||
useOtherRestoreTagMethod = true;
|
||||
LOGGER.warn("IllegalStateException attempting to populate tile entity into the world at location {},{},{}. " +
|
||||
"Possibly on <=1.17.1, switching to secondary method.", x, y, z, e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -113,9 +105,8 @@ public class LimitedRegionWrapperQueue extends DelegateQueueCoordinator {
|
||||
|
||||
@Override
|
||||
public boolean setTile(final int x, final int y, final int z, @NonNull final CompoundTag tag) {
|
||||
StateWrapper sw = new StateWrapper(tag);
|
||||
try {
|
||||
return sw.restoreTag(limitedRegion.getBlockState(x, y, z).getBlock());
|
||||
return StateWrapper.INSTANCE.restore(limitedRegion.getBlockState(x, y, z).getBlock(), tag);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.error("Error attempting to populate tile entity into the world at location {},{},{}", x, y, z, e);
|
||||
return false;
|
||||
|
@@ -27,6 +27,8 @@ import com.plotsquared.core.util.WorldUtil;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Schematic Handler.
|
||||
*/
|
||||
@@ -39,8 +41,8 @@ public class BukkitSchematicHandler extends SchematicHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean restoreTile(QueueCoordinator queue, CompoundTag ct, int x, int y, int z) {
|
||||
return new StateWrapper(ct).restoreTag(queue.getWorld().getName(), x, y, z);
|
||||
public boolean restoreTile(QueueCoordinator queue, CompoundTag tag, int x, int y, int z) {
|
||||
return StateWrapper.INSTANCE.restore(Objects.requireNonNull(queue.getWorld()).getName(), x, y, z, tag);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -18,367 +18,71 @@
|
||||
*/
|
||||
package com.plotsquared.bukkit.schematic;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.plotsquared.bukkit.util.BukkitUtil;
|
||||
import com.plotsquared.core.PlotSquared;
|
||||
import com.plotsquared.core.util.ReflectionUtils;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.Tag;
|
||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||
import com.sk89q.worldedit.extension.platform.NoCapablePlatformException;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.DyeColor;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Sign;
|
||||
import org.bukkit.block.sign.Side;
|
||||
import org.bukkit.block.sign.SignSide;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* This class (attempts to) restore block tile entity data, after the underlying block state has been placed.
|
||||
* This is used on chunk population (world generation) and in the platforms queue handler (as a fallback for WorldEdit placement).
|
||||
* <br />
|
||||
* This class relies heavily on reflective access, native minecraft methods and non-standardized WorldEdit / FAWE methods. It's
|
||||
* extremely prone to breakage between versions (Minecraft and/or (FA)WE), but supports most if not all possible tile entities.
|
||||
* Given the previous logic of this class was also non-reliable between version updates, and did only support a small subset of
|
||||
* tile entities, it's a fair trade-off.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public class StateWrapper {
|
||||
public sealed interface StateWrapper permits StateWrapperSpigot, StateWrapper.Factory.NoopStateWrapper {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + StateWrapper.class.getSimpleName());
|
||||
StateWrapper INSTANCE = Factory.createStateWrapper();
|
||||
|
||||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||
private static final Gson GSON = new GsonBuilder().registerTypeHierarchyAdapter(Tag.class, new NbtGsonSerializer()).create();
|
||||
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackageName();
|
||||
boolean restore(final @NonNull Block block, final @NonNull CompoundTag data);
|
||||
|
||||
private static final boolean FORCE_UPDATE_STATE = true;
|
||||
private static final boolean UPDATE_TRIGGER_PHYSICS = false;
|
||||
private static final boolean SUPPORTED = PlotSquared.platform().serverVersion()[1] > 20 ||
|
||||
(PlotSquared.platform().serverVersion()[1] == 20 && PlotSquared.platform().serverVersion()[2] >= 4);
|
||||
private static final String INITIALIZATION_ERROR_TEMPLATE = """
|
||||
Failed to initialize StateWrapper: {}
|
||||
Block-/Tile-Entities, pasted by schematics for example, won't be updated with their respective block data. This affects things like sign text, banner patterns, skulls, etc.
|
||||
Try updating your Server Software, PlotSquared and WorldEdit / FastAsyncWorldEdit first. If the issue persists, report it on the issue tracker.
|
||||
""";
|
||||
|
||||
private static boolean NOT_SUPPORTED_NOTIFIED = false;
|
||||
private static boolean FAILED_INITIALIZATION = false;
|
||||
private static BukkitImplAdapter ADAPTER = null;
|
||||
private static Class<?> LIN_TAG_CLASS = null;
|
||||
private static Class<?> JNBT_TAG_CLASS = null;
|
||||
private static Class<?> CRAFT_BLOCK_ENTITY_STATE_CLASS = null;
|
||||
private static MethodHandle PAPERWEIGHT_ADAPTER_FROM_NATIVE = null;
|
||||
private static MethodHandle CRAFT_BLOCK_ENTITY_STATE_LOAD_DATA = null;
|
||||
private static MethodHandle CRAFT_BLOCK_ENTITY_STATE_UPDATE = null;
|
||||
private static MethodHandle TO_LIN_TAG = null;
|
||||
|
||||
// SIGN HACK
|
||||
private static boolean PAPER_SIGN_NOTIFIED = false;
|
||||
private static boolean FAILED_SIGN_INITIALIZATION = false;
|
||||
private static Object KYORI_GSON_SERIALIZER = null;
|
||||
private static MethodHandle GSON_SERIALIZER_DESERIALIZE_TREE = null;
|
||||
private static MethodHandle BUKKIT_SIGN_SIDE_LINE_SET = null;
|
||||
|
||||
public CompoundTag tag;
|
||||
|
||||
public StateWrapper(CompoundTag tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the TileEntity data to the given world at the given coordinates.
|
||||
*
|
||||
* @param worldName World name
|
||||
* @param x x position
|
||||
* @param y y position
|
||||
* @param z z position
|
||||
* @return true if successful
|
||||
*/
|
||||
public boolean restoreTag(String worldName, int x, int y, int z) {
|
||||
World world = BukkitUtil.getWorld(worldName);
|
||||
default boolean restore(final String worldName, final int x, final int y, final int z, final CompoundTag data) {
|
||||
final World world = BukkitUtil.getWorld(worldName);
|
||||
if (world == null) {
|
||||
return false;
|
||||
}
|
||||
return restoreTag(world.getBlockAt(x, y, z));
|
||||
return this.restore(world.getBlockAt(x, y, z), data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the TileEntity data to the given block
|
||||
*
|
||||
* @param block Block to restore to
|
||||
* @return true if successful
|
||||
*/
|
||||
public boolean restoreTag(@NonNull Block block) {
|
||||
if (this.tag == null || FAILED_INITIALIZATION) {
|
||||
return false;
|
||||
}
|
||||
if (!SUPPORTED) {
|
||||
if (!NOT_SUPPORTED_NOTIFIED) {
|
||||
NOT_SUPPORTED_NOTIFIED = true;
|
||||
LOGGER.error(INITIALIZATION_ERROR_TEMPLATE, "Your server version is not supported. 1.20.4 or later is required");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (ADAPTER == null) {
|
||||
try {
|
||||
findNbtCompoundClassType(clazz -> LIN_TAG_CLASS = clazz, clazz -> JNBT_TAG_CLASS = clazz);
|
||||
ReflectionUtils.RefClass worldEditPluginRefClass = ReflectionUtils.getRefClass(WorldEditPlugin.class);
|
||||
WorldEditPlugin worldEditPlugin = (WorldEditPlugin) worldEditPluginRefClass
|
||||
.getMethod("getInstance")
|
||||
.of(null)
|
||||
.call();
|
||||
ADAPTER = (BukkitImplAdapter) worldEditPluginRefClass
|
||||
.getMethod("getBukkitImplAdapter")
|
||||
.of(worldEditPlugin)
|
||||
.call();
|
||||
PAPERWEIGHT_ADAPTER_FROM_NATIVE = findPaperweightAdapterFromNativeMethodHandle(
|
||||
ADAPTER.getClass(), LIN_TAG_CLASS, JNBT_TAG_CLASS
|
||||
);
|
||||
TO_LIN_TAG = findToLinTagMethodHandle(LIN_TAG_CLASS);
|
||||
} catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | NoCapablePlatformException e) {
|
||||
LOGGER.error(INITIALIZATION_ERROR_TEMPLATE, "Failed to access required WorldEdit methods", e);
|
||||
FAILED_INITIALIZATION = true;
|
||||
return false;
|
||||
@ApiStatus.Internal
|
||||
final class Factory {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + StateWrapper.class.getSimpleName());
|
||||
|
||||
private static final String INITIALIZATION_ERROR_TEMPLATE = """
|
||||
Failed to initialize StateWrapper: {}
|
||||
Block-/Tile-Entities, pasted by schematics for example, won't be updated with their respective block data. This affects things like sign text, banner patterns, skulls, etc.
|
||||
Try updating your Server Software, PlotSquared and WorldEdit / FastAsyncWorldEdit first. If the issue persists, report it on the issue tracker.
|
||||
""";
|
||||
|
||||
private static StateWrapper createStateWrapper() {
|
||||
int[] serverVersion = PlotSquared.platform().serverVersion();
|
||||
if (PaperLib.isPaper() && (serverVersion[1] == 21 && serverVersion[2] >= 5) || serverVersion[1] > 21) {
|
||||
try {
|
||||
return new StateWrapperPaper1_21_5();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to initialize Paper-specific state wrapper, falling back to Spigot", e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
CRAFT_BLOCK_ENTITY_STATE_CLASS = Class.forName(CRAFTBUKKIT_PACKAGE + ".block.CraftBlockEntityState");
|
||||
CRAFT_BLOCK_ENTITY_STATE_LOAD_DATA = findCraftBlockEntityStateLoadDataMethodHandle(CRAFT_BLOCK_ENTITY_STATE_CLASS);
|
||||
CRAFT_BLOCK_ENTITY_STATE_UPDATE = findCraftBlockEntityStateUpdateMethodHandle(CRAFT_BLOCK_ENTITY_STATE_CLASS);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
|
||||
LOGGER.error(INITIALIZATION_ERROR_TEMPLATE, "Failed to initialize required native method accessors", e);
|
||||
FAILED_INITIALIZATION = true;
|
||||
return new StateWrapperSpigot();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(INITIALIZATION_ERROR_TEMPLATE, StateWrapperSpigot.class.getSimpleName(), e);
|
||||
}
|
||||
return new NoopStateWrapper();
|
||||
}
|
||||
|
||||
|
||||
@ApiStatus.Internal
|
||||
static final class NoopStateWrapper implements StateWrapper {
|
||||
|
||||
@Override
|
||||
public boolean restore(final @NonNull Block block, final @NonNull CompoundTag data) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
final BlockState blockState = block.getState();
|
||||
if (!CRAFT_BLOCK_ENTITY_STATE_CLASS.isAssignableFrom(blockState.getClass())) {
|
||||
return false;
|
||||
}
|
||||
// get native tag
|
||||
Object nativeTag = PAPERWEIGHT_ADAPTER_FROM_NATIVE.invoke(
|
||||
ADAPTER,
|
||||
LIN_TAG_CLASS == null ? this.tag : TO_LIN_TAG.invoke(this.tag)
|
||||
);
|
||||
CRAFT_BLOCK_ENTITY_STATE_LOAD_DATA.invoke(blockState, nativeTag);
|
||||
if (blockState instanceof Sign sign) {
|
||||
if (!PaperLib.isPaper()) {
|
||||
if (!PAPER_SIGN_NOTIFIED) {
|
||||
PAPER_SIGN_NOTIFIED = true;
|
||||
LOGGER.error("PlotSquared can't populate sign tile entities. To load sign content, use Paper.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Object text;
|
||||
if ((text = tag.getValue().get("front_text")) != null && text instanceof CompoundTag textTag) {
|
||||
setSignTextHack(sign, textTag, true);
|
||||
}
|
||||
if ((text = tag.getValue().get("back_text")) != null && text instanceof CompoundTag textTag) {
|
||||
setSignTextHack(sign, textTag, false);
|
||||
}
|
||||
}
|
||||
CRAFT_BLOCK_ENTITY_STATE_UPDATE.invoke(blockState, FORCE_UPDATE_STATE, UPDATE_TRIGGER_PHYSICS);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error("Failed to update tile entity", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sign content on the bukkit tile entity. The server does not load sign content applied via the main logic
|
||||
* (CraftBlockEntity#load), as the SignEntity needs to have a valid ServerLevel assigned to it.
|
||||
* That's not possible on worldgen; therefore, this hack has to be used additionally.
|
||||
* <br />
|
||||
* Modern sign content (non-plain-text sign lines) require Paper.
|
||||
*
|
||||
* @param sign The sign to apply data onto.
|
||||
* @param text The compound tag containing the data for the sign side ({@code front_text} / {@code back_text})
|
||||
* @param front If the compound tag contains the data for the front side.
|
||||
* @throws Throwable if something went wrong when reflectively updating the sign.
|
||||
*/
|
||||
private static void setSignTextHack(Sign sign, CompoundTag text, boolean front) throws Throwable {
|
||||
final SignSide side = sign.getSide(front ? Side.FRONT : Side.BACK);
|
||||
if (text.containsKey("color")) {
|
||||
//noinspection UnstableApiUsage
|
||||
side.setColor(DyeColor.legacyValueOf(text.getString("color").toUpperCase(Locale.ROOT)));
|
||||
}
|
||||
if (text.containsKey("has_glowing_text")) {
|
||||
side.setGlowingText(text.getByte("has_glowing_text") == 1);
|
||||
}
|
||||
if (!initializeSignHack()) {
|
||||
return;
|
||||
}
|
||||
// TODO: Pre 1.21.5 sign texts are JSON in string tags... somehow support and fix that
|
||||
List<Tag> lines = text.getList("messages");
|
||||
if (lines != null) {
|
||||
for (int i = 0; i < Math.min(lines.size(), 3); i++) {
|
||||
Tag line = lines.get(i);
|
||||
Object content = line.getValue();
|
||||
// Minecraft uses mixed lists / arrays in their sign texts. One line can be a complex component, whereas
|
||||
// the following line could simply be a string. Those simpler lines are represented as `{"": ""}` (only in
|
||||
// SNBT those will be shown as a standard string).
|
||||
if (line instanceof CompoundTag compoundTag && compoundTag.getValue().containsKey("")) {
|
||||
content = compoundTag.getValue().get("");
|
||||
}
|
||||
// serializes the line content from JNBT to Gson JSON objects, passes that to adventure and deserializes
|
||||
// into an adventure component.
|
||||
// pass all possible types of content into the deserializer (Strings, Compounds, Arrays), even though Strings
|
||||
// could be set directly via Sign#setLine(int, String). The overhead is minimal, the serializer can handle
|
||||
// strings - and we don't have to use the deprecated method.
|
||||
BUKKIT_SIGN_SIDE_LINE_SET.invoke(
|
||||
side, i, GSON_SERIALIZER_DESERIALIZE_TREE.invoke(
|
||||
KYORI_GSON_SERIALIZER,
|
||||
GSON.toJsonTree(content)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean initializeSignHack() {
|
||||
if (FAILED_SIGN_INITIALIZATION) {
|
||||
return false;
|
||||
}
|
||||
if (KYORI_GSON_SERIALIZER != null) {
|
||||
return true; // already initialized
|
||||
}
|
||||
try {
|
||||
char[] dontObfuscate = new char[]{
|
||||
'n', 'e', 't', '.', 'k', 'y', 'o', 'r', 'i', '.', 'a', 'd', 'v', 'e', 'n', 't', 'u', 'r', 'e', '.',
|
||||
't', 'e', 'x', 't', '.', 's', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'e', 'r', '.', 'g', 's', 'o', 'n', '.',
|
||||
'G', 's', 'o', 'n', 'C', 'o', 'm', 'p', 'o', 'n', 'e', 'n', 't', 'S', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'e', 'r'
|
||||
};
|
||||
Class<?> gsonComponentSerializerClass = Class.forName(new String(dontObfuscate));
|
||||
KYORI_GSON_SERIALIZER = Arrays.stream(gsonComponentSerializerClass.getMethods())
|
||||
.filter(method -> method.getName().equals("gson"))
|
||||
.findFirst()
|
||||
.orElseThrow().invoke(null);
|
||||
GSON_SERIALIZER_DESERIALIZE_TREE = LOOKUP.unreflect(Arrays
|
||||
.stream(gsonComponentSerializerClass.getMethods())
|
||||
.filter(method -> method.getName().equals("deserializeFromTree") && method.getParameterCount() == 1)
|
||||
.findFirst()
|
||||
.orElseThrow());
|
||||
BUKKIT_SIGN_SIDE_LINE_SET = LOOKUP.unreflect(Arrays.stream(SignSide.class.getMethods())
|
||||
.filter(method -> method.getName().equals("line") && method.getParameterCount() == 2)
|
||||
.findFirst()
|
||||
.orElseThrow());
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
FAILED_SIGN_INITIALIZATION = true;
|
||||
LOGGER.error("Failed to initialize sign-hack. Signs populated by schematics won't have their line contents.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the used NBT tag class. For modern FAWE and WE that'll be Lin - for older ones JNBT.
|
||||
*
|
||||
* @throws ClassNotFoundException if neither can be found.
|
||||
*/
|
||||
private static void findNbtCompoundClassType(Consumer<Class<?>> linClass, Consumer<Class<?>> jnbtClass) throws
|
||||
ClassNotFoundException {
|
||||
try {
|
||||
linClass.accept(Class.forName("org.enginehub.linbus.tree.LinTag"));
|
||||
} catch (ClassNotFoundException e) {
|
||||
jnbtClass.accept(Class.forName("com.sk89q.jnbt.Tag"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the {@code toLinTag} method on the {@code ToLinTag} interface, if lin-bus is available in the classpath.
|
||||
* <br />
|
||||
* Required to access the underlying lin tag of the used JNBT tag by PlotSquared, so it can be converted into the platforms
|
||||
* native tag later.
|
||||
*
|
||||
* @param linTagClass {@code Tag} class of lin-bus, or {@code null} if not available.
|
||||
* @return the MethodHandle for {@code toLinTag}, or {@code null} if lin-bus is not available in the classpath.
|
||||
* @throws ClassNotFoundException if the {@code ToLinTag} class could not be found.
|
||||
* @throws NoSuchMethodException if no {@code toLinTag} method exists.
|
||||
* @throws IllegalAccessException shouldn't happen.
|
||||
*/
|
||||
private static MethodHandle findToLinTagMethodHandle(Class<?> linTagClass) throws ClassNotFoundException,
|
||||
NoSuchMethodException, IllegalAccessException {
|
||||
if (linTagClass == null) {
|
||||
return null;
|
||||
}
|
||||
return LOOKUP.findVirtual(
|
||||
Class.forName("org.enginehub.linbus.tree.ToLinTag"),
|
||||
"toLinTag",
|
||||
MethodType.methodType(linTagClass)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the method (handle) to convert from native (= WE/FAWE) NBT tags to minecraft NBT tags.
|
||||
* <br />
|
||||
* Depending on the used version of WE/FAWE, this differs:
|
||||
* <ul>
|
||||
* <li>On WE/FAWE version pre LinBus introduction: {@code fromNative(org.sk89q.jnbt.Tag)}</li>
|
||||
* <li>On WE versions post LinBus introduction: {@code fromNative(org.enginehub.linbus.tree.LinTag)}</li>
|
||||
* <li>On FAWE versions post LinBus introduction: {@code fromNativeLin(org.enginehub.linbus.tree.LinTag)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param adapterClass The bukkit adapter implementation class
|
||||
* @param linTagClass The lin-bus {@code Tag} class, if existing - otherwise {@code null}
|
||||
* @param jnbtTagClass The jnbt {@code Tag} class, if lin-bus was not found in classpath - otherwise {@code null}
|
||||
* @return the method.
|
||||
* @throws IllegalAccessException shouldn't happen as private lookup is used.
|
||||
* @throws NoSuchMethodException if the method couldn't be found.
|
||||
*/
|
||||
private static MethodHandle findPaperweightAdapterFromNativeMethodHandle(
|
||||
Class<?> adapterClass, Class<?> linTagClass, Class<?> jnbtTagClass
|
||||
) throws IllegalAccessException, NoSuchMethodException {
|
||||
final MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(adapterClass, LOOKUP);
|
||||
if (jnbtTagClass != null) {
|
||||
// usage of JNBT = identical method signatures for WE and FAWE
|
||||
return lookup.findVirtual(adapterClass, "fromNative", MethodType.methodType(Object.class, jnbtTagClass));
|
||||
}
|
||||
try {
|
||||
// FAWE
|
||||
return lookup.findVirtual(adapterClass, "fromNativeLin", MethodType.methodType(Object.class, linTagClass));
|
||||
} catch (NoSuchMethodException e) {
|
||||
// WE
|
||||
return lookup.findVirtual(adapterClass, "fromNative", MethodType.methodType(Object.class, linTagClass));
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodHandle findCraftBlockEntityStateLoadDataMethodHandle(Class<?> craftBlockEntityStateClass) throws
|
||||
NoSuchMethodException, IllegalAccessException, ClassNotFoundException {
|
||||
for (final Method method : craftBlockEntityStateClass.getMethods()) {
|
||||
if (method.getName().equals("loadData") && method.getParameterCount() == 1) {
|
||||
return LOOKUP.unreflect(method);
|
||||
}
|
||||
}
|
||||
throw new NoSuchMethodException("Couldn't find #loadData(CompoundTag) in " + craftBlockEntityStateClass.getName());
|
||||
}
|
||||
|
||||
private static MethodHandle findCraftBlockEntityStateUpdateMethodHandle(Class<?> craftBlockEntityStateClass) throws
|
||||
NoSuchMethodException, IllegalAccessException, ClassNotFoundException {
|
||||
for (final Method method : craftBlockEntityStateClass.getMethods()) {
|
||||
if (method.getReturnType().equals(Boolean.TYPE) && method.getParameterCount() == 2 &&
|
||||
method.getParameterTypes()[0] == Boolean.TYPE && method.getParameterTypes()[1] == Boolean.TYPE) {
|
||||
return LOOKUP.unreflect(method);
|
||||
}
|
||||
}
|
||||
throw new NoSuchMethodException("Couldn't find method for #update(boolean, boolean) in " + craftBlockEntityStateClass.getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* PlotSquared, a land and world management plugin for Minecraft.
|
||||
* Copyright (C) IntellectualSites <https://intellectualsites.com>
|
||||
* Copyright (C) IntellectualSites team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.plotsquared.bukkit.schematic;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.Tag;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bukkit.DyeColor;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.block.Sign;
|
||||
import org.bukkit.block.sign.Side;
|
||||
import org.bukkit.block.sign.SignSide;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
final class StateWrapperPaper1_21_5 extends StateWrapperSpigot {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + StateWrapperPaper1_21_5.class.getSimpleName());
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().registerTypeHierarchyAdapter(Tag.class, new NbtGsonSerializer()).create();
|
||||
private static Object KYORI_GSON_SERIALIZER = null;
|
||||
private static MethodHandle GSON_SERIALIZER_DESERIALIZE_TREE = null;
|
||||
private static MethodHandle BUKKIT_SIGN_SIDE_LINE_SET = null;
|
||||
|
||||
public StateWrapperPaper1_21_5() {
|
||||
super();
|
||||
try {
|
||||
initializeSignHack();
|
||||
LOGGER.info("Using {} for block data population", StateWrapperPaper1_21_5.class.getSimpleName());
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException("Failed to initialize sign hack", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postEntityStateLoad(final @NonNull BlockState blockState, final @NonNull CompoundTag data) throws Throwable {
|
||||
// signs need special handling during generation
|
||||
if (blockState instanceof Sign sign) {
|
||||
if (data.getValue().get("front_text") instanceof CompoundTag textTag) {
|
||||
setSignTextHack(sign.getSide(Side.FRONT), textTag);
|
||||
}
|
||||
if (data.getValue().get("back_text") instanceof CompoundTag textTag) {
|
||||
setSignTextHack(sign.getSide(Side.BACK), textTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger logger() {
|
||||
return StateWrapperPaper1_21_5.LOGGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sign content on the bukkit tile entity. The server does not load sign content applied via the main logic
|
||||
* (CraftBlockEntity#load), as the SignEntity needs to have a valid ServerLevel assigned to it.
|
||||
* That's not possible on worldgen; therefore, this hack has to be used additionally.
|
||||
* <br />
|
||||
* Modern sign content (non-plain-text sign lines) require Paper.
|
||||
*
|
||||
* @param side The sign side to apply data onto.
|
||||
* @param text The compound tag containing the data for the sign side ({@code front_text} / {@code back_text})
|
||||
* @throws Throwable if something went wrong when reflectively updating the sign.
|
||||
*/
|
||||
private static void setSignTextHack(SignSide side, CompoundTag text) throws Throwable {
|
||||
if (text.containsKey("color")) {
|
||||
//noinspection UnstableApiUsage
|
||||
side.setColor(DyeColor.legacyValueOf(text.getString("color").toUpperCase(Locale.ROOT)));
|
||||
}
|
||||
if (text.containsKey("has_glowing_text")) {
|
||||
side.setGlowingText(text.getByte("has_glowing_text") == 1);
|
||||
}
|
||||
List<Tag> lines = text.getList("messages");
|
||||
if (lines != null) {
|
||||
for (int i = 0; i < Math.min(lines.size(), 3); i++) {
|
||||
Tag line = lines.get(i);
|
||||
Object content = line.getValue();
|
||||
// Minecraft uses mixed lists / arrays in their sign texts. One line can be a complex component, whereas
|
||||
// the following line could simply be a string. Those simpler lines are represented as `{"": ""}` (only in
|
||||
// SNBT those will be shown as a standard string).
|
||||
if (line instanceof CompoundTag compoundTag && compoundTag.getValue().containsKey("")) {
|
||||
content = compoundTag.getValue().get("");
|
||||
}
|
||||
// absolute garbage way to try to handle stringified components (pre 1.21.5)
|
||||
else if (content instanceof String contentAsString && (contentAsString.startsWith("{") || contentAsString.startsWith("["))) {
|
||||
try {
|
||||
content = JsonParser.parseString(contentAsString);
|
||||
} catch (JsonSyntaxException e) {
|
||||
// well, it wasn't JSON after all
|
||||
}
|
||||
}
|
||||
|
||||
// serializes the line content from JNBT to Gson JSON objects, passes that to adventure and deserializes
|
||||
// into an adventure component.
|
||||
// pass all possible types of content into the deserializer (Strings, Compounds, Arrays), even though Strings
|
||||
// could be set directly via Sign#setLine(int, String). The overhead is minimal, the serializer can handle
|
||||
// strings - and we don't have to use the deprecated method.
|
||||
BUKKIT_SIGN_SIDE_LINE_SET.invoke(
|
||||
side, i, GSON_SERIALIZER_DESERIALIZE_TREE.invoke(
|
||||
KYORI_GSON_SERIALIZER,
|
||||
content instanceof JsonElement ? content : GSON.toJsonTree(content)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void initializeSignHack() throws Throwable {
|
||||
char[] dontObfuscate = new char[]{
|
||||
'n', 'e', 't', '.', 'k', 'y', 'o', 'r', 'i', '.', 'a', 'd', 'v', 'e', 'n', 't', 'u', 'r', 'e', '.',
|
||||
't', 'e', 'x', 't', '.', 's', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'e', 'r', '.', 'g', 's', 'o', 'n', '.',
|
||||
'G', 's', 'o', 'n', 'C', 'o', 'm', 'p', 'o', 'n', 'e', 'n', 't', 'S', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'e', 'r'
|
||||
};
|
||||
Class<?> gsonComponentSerializerClass = Class.forName(new String(dontObfuscate));
|
||||
KYORI_GSON_SERIALIZER = Arrays.stream(gsonComponentSerializerClass.getMethods())
|
||||
.filter(method -> method.getName().equals("gson"))
|
||||
.findFirst()
|
||||
.orElseThrow().invoke(null);
|
||||
GSON_SERIALIZER_DESERIALIZE_TREE = LOOKUP.unreflect(Arrays
|
||||
.stream(gsonComponentSerializerClass.getMethods())
|
||||
.filter(method -> method.getName().equals("deserializeFromTree") && method.getParameterCount() == 1)
|
||||
.findFirst()
|
||||
.orElseThrow());
|
||||
BUKKIT_SIGN_SIDE_LINE_SET = LOOKUP.unreflect(Arrays.stream(SignSide.class.getMethods())
|
||||
.filter(method -> method.getName().equals("line") && method.getParameterCount() == 2)
|
||||
.findFirst()
|
||||
.orElseThrow());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* PlotSquared, a land and world management plugin for Minecraft.
|
||||
* Copyright (C) IntellectualSites <https://intellectualsites.com>
|
||||
* Copyright (C) IntellectualSites team and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.plotsquared.bukkit.schematic;
|
||||
|
||||
import com.plotsquared.core.util.ReflectionUtils;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
|
||||
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
|
||||
import com.sk89q.worldedit.extension.platform.NoCapablePlatformException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
sealed class StateWrapperSpigot implements StateWrapper permits StateWrapperPaper1_21_5 {
|
||||
|
||||
private static final boolean FORCE_UPDATE_STATE = true;
|
||||
private static final boolean UPDATE_TRIGGER_PHYSICS = false;
|
||||
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackageName();
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + StateWrapperSpigot.class.getSimpleName());
|
||||
static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||
|
||||
private static BukkitImplAdapter ADAPTER = null;
|
||||
private static Class<?> LIN_TAG_CLASS = null;
|
||||
private static Class<?> JNBT_TAG_CLASS = null;
|
||||
private static Class<?> CRAFT_BLOCK_ENTITY_STATE_CLASS = null;
|
||||
private static MethodHandle PAPERWEIGHT_ADAPTER_FROM_NATIVE = null;
|
||||
private static MethodHandle CRAFT_BLOCK_ENTITY_STATE_LOAD_DATA = null;
|
||||
private static MethodHandle CRAFT_BLOCK_ENTITY_STATE_UPDATE = null;
|
||||
private static MethodHandle TO_LIN_TAG = null;
|
||||
|
||||
public StateWrapperSpigot() {
|
||||
try {
|
||||
findNbtCompoundClassType(clazz -> LIN_TAG_CLASS = clazz, clazz -> JNBT_TAG_CLASS = clazz);
|
||||
ReflectionUtils.RefClass worldEditPluginRefClass = ReflectionUtils.getRefClass(WorldEditPlugin.class);
|
||||
WorldEditPlugin worldEditPlugin = (WorldEditPlugin) worldEditPluginRefClass
|
||||
.getMethod("getInstance")
|
||||
.of(null)
|
||||
.call();
|
||||
ADAPTER = (BukkitImplAdapter) worldEditPluginRefClass
|
||||
.getMethod("getBukkitImplAdapter")
|
||||
.of(worldEditPlugin)
|
||||
.call();
|
||||
PAPERWEIGHT_ADAPTER_FROM_NATIVE = findPaperweightAdapterFromNativeMethodHandle(
|
||||
ADAPTER.getClass(), LIN_TAG_CLASS, JNBT_TAG_CLASS
|
||||
);
|
||||
TO_LIN_TAG = findToLinTagMethodHandle(LIN_TAG_CLASS);
|
||||
} catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | NoCapablePlatformException e) {
|
||||
throw new RuntimeException("Failed to access required WorldEdit methods", e);
|
||||
}
|
||||
try {
|
||||
CRAFT_BLOCK_ENTITY_STATE_CLASS = Class.forName(CRAFTBUKKIT_PACKAGE + ".block.CraftBlockEntityState");
|
||||
CRAFT_BLOCK_ENTITY_STATE_LOAD_DATA = findCraftBlockEntityStateLoadDataMethodHandle(CRAFT_BLOCK_ENTITY_STATE_CLASS);
|
||||
CRAFT_BLOCK_ENTITY_STATE_UPDATE = findCraftBlockEntityStateUpdateMethodHandle(CRAFT_BLOCK_ENTITY_STATE_CLASS);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Failed to initialize required native method accessors", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean restore(final @NonNull Block block, final @NonNull CompoundTag data) {
|
||||
try {
|
||||
final BlockState blockState = block.getState();
|
||||
if (!CRAFT_BLOCK_ENTITY_STATE_CLASS.isAssignableFrom(blockState.getClass())) {
|
||||
return false;
|
||||
}
|
||||
// get native tag
|
||||
Object nativeTag = PAPERWEIGHT_ADAPTER_FROM_NATIVE.invoke(
|
||||
ADAPTER,
|
||||
LIN_TAG_CLASS == null ? data : TO_LIN_TAG.invoke(data)
|
||||
);
|
||||
// load block entity data
|
||||
CRAFT_BLOCK_ENTITY_STATE_LOAD_DATA.invoke(blockState, nativeTag);
|
||||
|
||||
postEntityStateLoad(blockState, data);
|
||||
|
||||
CRAFT_BLOCK_ENTITY_STATE_UPDATE.invoke(blockState, FORCE_UPDATE_STATE, UPDATE_TRIGGER_PHYSICS);
|
||||
} catch (Throwable e) {
|
||||
logger().error("Failed to update tile entity", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void postEntityStateLoad(final @NonNull BlockState blockState, final @NonNull CompoundTag data) throws Throwable {
|
||||
|
||||
}
|
||||
|
||||
public Logger logger() {
|
||||
return StateWrapperSpigot.LOGGER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the used NBT tag class. For modern FAWE and WE that'll be Lin - for older ones JNBT.
|
||||
*
|
||||
* @throws ClassNotFoundException if neither can be found.
|
||||
*/
|
||||
private static void findNbtCompoundClassType(Consumer<Class<?>> linClass, Consumer<Class<?>> jnbtClass) throws
|
||||
ClassNotFoundException {
|
||||
try {
|
||||
linClass.accept(Class.forName("org.enginehub.linbus.tree.LinTag"));
|
||||
} catch (ClassNotFoundException e) {
|
||||
jnbtClass.accept(Class.forName("com.sk89q.jnbt.Tag"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the {@code toLinTag} method on the {@code ToLinTag} interface, if lin-bus is available in the classpath.
|
||||
* <br />
|
||||
* Required to access the underlying lin tag of the used JNBT tag by PlotSquared, so it can be converted into the platforms
|
||||
* native tag later.
|
||||
*
|
||||
* @param linTagClass {@code Tag} class of lin-bus, or {@code null} if not available.
|
||||
* @return the MethodHandle for {@code toLinTag}, or {@code null} if lin-bus is not available in the classpath.
|
||||
* @throws ClassNotFoundException if the {@code ToLinTag} class could not be found.
|
||||
* @throws NoSuchMethodException if no {@code toLinTag} method exists.
|
||||
* @throws IllegalAccessException shouldn't happen.
|
||||
*/
|
||||
private static MethodHandle findToLinTagMethodHandle(Class<?> linTagClass) throws ClassNotFoundException,
|
||||
NoSuchMethodException, IllegalAccessException {
|
||||
if (linTagClass == null) {
|
||||
return null;
|
||||
}
|
||||
return LOOKUP.findVirtual(
|
||||
Class.forName("org.enginehub.linbus.tree.ToLinTag"),
|
||||
"toLinTag",
|
||||
MethodType.methodType(linTagClass)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the method (handle) to convert from native (= WE/FAWE) NBT tags to minecraft NBT tags.
|
||||
* <br />
|
||||
* Depending on the used version of WE/FAWE, this differs:
|
||||
* <ul>
|
||||
* <li>On WE/FAWE version pre LinBus introduction: {@code fromNative(org.sk89q.jnbt.Tag)}</li>
|
||||
* <li>On WE versions post LinBus introduction: {@code fromNative(org.enginehub.linbus.tree.LinTag)}</li>
|
||||
* <li>On FAWE versions post LinBus introduction: {@code fromNativeLin(org.enginehub.linbus.tree.LinTag)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param adapterClass The bukkit adapter implementation class
|
||||
* @param linTagClass The lin-bus {@code Tag} class, if existing - otherwise {@code null}
|
||||
* @param jnbtTagClass The jnbt {@code Tag} class, if lin-bus was not found in classpath - otherwise {@code null}
|
||||
* @return the method.
|
||||
* @throws IllegalAccessException shouldn't happen as private lookup is used.
|
||||
* @throws NoSuchMethodException if the method couldn't be found.
|
||||
*/
|
||||
private static MethodHandle findPaperweightAdapterFromNativeMethodHandle(
|
||||
Class<?> adapterClass, Class<?> linTagClass, Class<?> jnbtTagClass
|
||||
) throws IllegalAccessException, NoSuchMethodException {
|
||||
final MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(adapterClass, LOOKUP);
|
||||
if (jnbtTagClass != null) {
|
||||
// usage of JNBT = identical method signatures for WE and FAWE
|
||||
return lookup.findVirtual(adapterClass, "fromNative", MethodType.methodType(Object.class, jnbtTagClass));
|
||||
}
|
||||
try {
|
||||
// FAWE
|
||||
return lookup.findVirtual(adapterClass, "fromNativeLin", MethodType.methodType(Object.class, linTagClass));
|
||||
} catch (NoSuchMethodException e) {
|
||||
// WE
|
||||
return lookup.findVirtual(adapterClass, "fromNative", MethodType.methodType(Object.class, linTagClass));
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodHandle findCraftBlockEntityStateLoadDataMethodHandle(Class<?> craftBlockEntityStateClass) throws
|
||||
NoSuchMethodException, IllegalAccessException, ClassNotFoundException {
|
||||
for (final Method method : craftBlockEntityStateClass.getMethods()) {
|
||||
if (method.getName().equals("loadData") && method.getParameterCount() == 1) {
|
||||
return LOOKUP.unreflect(method);
|
||||
}
|
||||
}
|
||||
throw new NoSuchMethodException("Couldn't find #loadData(CompoundTag) in " + craftBlockEntityStateClass.getName());
|
||||
}
|
||||
|
||||
private static MethodHandle findCraftBlockEntityStateUpdateMethodHandle(Class<?> craftBlockEntityStateClass) throws
|
||||
NoSuchMethodException, IllegalAccessException, ClassNotFoundException {
|
||||
for (final Method method : craftBlockEntityStateClass.getMethods()) {
|
||||
if (method.getReturnType().equals(Boolean.TYPE) && method.getParameterCount() == 2 &&
|
||||
method.getParameterTypes()[0] == Boolean.TYPE && method.getParameterTypes()[1] == Boolean.TYPE) {
|
||||
return LOOKUP.unreflect(method);
|
||||
}
|
||||
}
|
||||
throw new NoSuchMethodException("Couldn't find method for #update(boolean, boolean) in " + craftBlockEntityStateClass.getName());
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user