mirror of
https://github.com/IntellectualSites/PlotSquared.git
synced 2025-09-08 13:55:36 +02:00
fix: signs with component lines
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
package com.plotsquared.bukkit.schematic;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.JsonSerializationContext;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
import com.sk89q.jnbt.ByteArrayTag;
|
||||||
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.jnbt.IntArrayTag;
|
||||||
|
import com.sk89q.jnbt.ListTag;
|
||||||
|
import com.sk89q.jnbt.LongArrayTag;
|
||||||
|
import com.sk89q.jnbt.Tag;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
final class NbtGsonSerializer implements JsonSerializer<Tag> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(final Tag src, final Type typeOfSrc, final JsonSerializationContext context) {
|
||||||
|
if (src instanceof CompoundTag compoundTag) {
|
||||||
|
JsonObject object = new JsonObject();
|
||||||
|
compoundTag.getValue().forEach((s, tag) -> object.add(s, context.serialize(tag)));
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
if (src instanceof ListTag listTag) {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
listTag.getValue().forEach(tag -> array.add(context.serialize(tag)));
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
if (src instanceof ByteArrayTag byteArrayTag) {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
for (final byte b : byteArrayTag.getValue()) {
|
||||||
|
array.add(b);
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
if (src instanceof IntArrayTag intArrayTag) {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
for (final int i : intArrayTag.getValue()) {
|
||||||
|
array.add(i);
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
if (src instanceof LongArrayTag longArrayTag) {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
for (final long l : longArrayTag.getValue()) {
|
||||||
|
array.add(l);
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
if (src.getValue() instanceof Number number) {
|
||||||
|
return new JsonPrimitive(number);
|
||||||
|
}
|
||||||
|
if (src.getValue() instanceof String string) {
|
||||||
|
return new JsonPrimitive(string);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Don't know how to serialize " + src);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -19,7 +19,9 @@
|
|||||||
package com.plotsquared.bukkit.schematic;
|
package com.plotsquared.bukkit.schematic;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
import com.plotsquared.bukkit.util.BukkitUtil;
|
import com.plotsquared.bukkit.util.BukkitUtil;
|
||||||
|
import com.plotsquared.core.PlotSquared;
|
||||||
import com.plotsquared.core.util.ReflectionUtils;
|
import com.plotsquared.core.util.ReflectionUtils;
|
||||||
import com.sk89q.jnbt.CompoundTag;
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
import com.sk89q.jnbt.ListTag;
|
import com.sk89q.jnbt.ListTag;
|
||||||
@@ -66,17 +68,20 @@ public class StateWrapper {
|
|||||||
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + StateWrapper.class.getSimpleName());
|
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + StateWrapper.class.getSimpleName());
|
||||||
|
|
||||||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||||
private static final Gson GSON = new Gson();
|
private static final Gson GSON = new GsonBuilder().registerTypeHierarchyAdapter(Tag.class, new NbtGsonSerializer()).create();
|
||||||
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackageName();
|
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackageName();
|
||||||
|
|
||||||
private static final boolean FORCE_UPDATE_STATE = true;
|
private static final boolean FORCE_UPDATE_STATE = true;
|
||||||
private static final boolean UPDATE_TRIGGER_PHYSICS = false;
|
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 = """
|
private static final String INITIALIZATION_ERROR_TEMPLATE = """
|
||||||
Failed to initialize StateWrapper: %s
|
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.
|
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.
|
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 boolean FAILED_INITIALIZATION = false;
|
||||||
private static BukkitImplAdapter ADAPTER = null;
|
private static BukkitImplAdapter ADAPTER = null;
|
||||||
private static Class<?> LIN_TAG_CLASS = null;
|
private static Class<?> LIN_TAG_CLASS = null;
|
||||||
@@ -127,6 +132,13 @@ public class StateWrapper {
|
|||||||
if (this.tag == null || FAILED_INITIALIZATION) {
|
if (this.tag == null || FAILED_INITIALIZATION) {
|
||||||
return false;
|
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) {
|
if (ADAPTER == null) {
|
||||||
try {
|
try {
|
||||||
findNbtCompoundClassType(clazz -> LIN_TAG_CLASS = clazz, clazz -> JNBT_TAG_CLASS = clazz);
|
findNbtCompoundClassType(clazz -> LIN_TAG_CLASS = clazz, clazz -> JNBT_TAG_CLASS = clazz);
|
||||||
@@ -144,7 +156,7 @@ public class StateWrapper {
|
|||||||
);
|
);
|
||||||
TO_LIN_TAG = findToLinTagMethodHandle(LIN_TAG_CLASS);
|
TO_LIN_TAG = findToLinTagMethodHandle(LIN_TAG_CLASS);
|
||||||
} catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | NoCapablePlatformException e) {
|
} catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | NoCapablePlatformException e) {
|
||||||
LOGGER.error(INITIALIZATION_ERROR_TEMPLATE.formatted("Failed to access required WorldEdit methods"), e);
|
LOGGER.error(INITIALIZATION_ERROR_TEMPLATE, "Failed to access required WorldEdit methods", e);
|
||||||
FAILED_INITIALIZATION = true;
|
FAILED_INITIALIZATION = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -153,7 +165,7 @@ public class StateWrapper {
|
|||||||
CRAFT_BLOCK_ENTITY_STATE_LOAD_DATA = findCraftBlockEntityStateLoadDataMethodHandle(CRAFT_BLOCK_ENTITY_STATE_CLASS);
|
CRAFT_BLOCK_ENTITY_STATE_LOAD_DATA = findCraftBlockEntityStateLoadDataMethodHandle(CRAFT_BLOCK_ENTITY_STATE_CLASS);
|
||||||
CRAFT_BLOCK_ENTITY_STATE_UPDATE = findCraftBlockEntityStateUpdateMethodHandle(CRAFT_BLOCK_ENTITY_STATE_CLASS);
|
CRAFT_BLOCK_ENTITY_STATE_UPDATE = findCraftBlockEntityStateUpdateMethodHandle(CRAFT_BLOCK_ENTITY_STATE_CLASS);
|
||||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
|
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
|
||||||
LOGGER.error(INITIALIZATION_ERROR_TEMPLATE.formatted("Failed to initialize required native method accessors"), e);
|
LOGGER.error(INITIALIZATION_ERROR_TEMPLATE, "Failed to initialize required native method accessors", e);
|
||||||
FAILED_INITIALIZATION = true;
|
FAILED_INITIALIZATION = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -204,7 +216,7 @@ public class StateWrapper {
|
|||||||
side.setColor(DyeColor.legacyValueOf(text.getString("color").toUpperCase(Locale.ROOT)));
|
side.setColor(DyeColor.legacyValueOf(text.getString("color").toUpperCase(Locale.ROOT)));
|
||||||
}
|
}
|
||||||
if (text.containsKey("has_glowing_text")) {
|
if (text.containsKey("has_glowing_text")) {
|
||||||
side.setGlowingText(text.getByte("has_glowing_text") == 0x1b);
|
side.setGlowingText(text.getByte("has_glowing_text") == 1);
|
||||||
}
|
}
|
||||||
List<Tag> lines = text.getList("messages");
|
List<Tag> lines = text.getList("messages");
|
||||||
if (lines != null) {
|
if (lines != null) {
|
||||||
@@ -219,11 +231,22 @@ public class StateWrapper {
|
|||||||
if (!initializeSignHack()) {
|
if (!initializeSignHack()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final Object component = GSON_SERIALIZER_DESERIALIZE_TREE.invoke(
|
// Minecraft uses mixed lists / arrays in their sign texts. One line can be a complex component, whereas
|
||||||
KYORI_GSON_SERIALIZER,
|
// the following line could simply be a string. Those simpler lines are represented as `{"": ""}` (only in
|
||||||
GSON.toJsonTree(line.getValue())
|
// SNBT those will be shown as a standard string). Adventure can't parse those, so we handle these lines as
|
||||||
|
// plaintext lines (can't contain any other extra data either way).
|
||||||
|
if (line instanceof CompoundTag compoundTag && compoundTag.getValue().containsKey("")) {
|
||||||
|
//noinspection deprecation - Paper deprecatiom
|
||||||
|
side.setLine(i, compoundTag.getString(""));
|
||||||
|
}
|
||||||
|
// serializes the line content from JNBT to Gson JSON objects, passes that to adventure and deserializes
|
||||||
|
// into an adventure component.
|
||||||
|
BUKKIT_SIGN_SIDE_LINE_SET.invoke(
|
||||||
|
side, i, GSON_SERIALIZER_DESERIALIZE_TREE.invoke(
|
||||||
|
KYORI_GSON_SERIALIZER,
|
||||||
|
GSON.toJsonTree(line.getValue())
|
||||||
|
)
|
||||||
);
|
);
|
||||||
BUKKIT_SIGN_SIDE_LINE_SET.invoke(side, i, component);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,6 +256,9 @@ public class StateWrapper {
|
|||||||
if (FAILED_SIGN_INITIALIZATION) {
|
if (FAILED_SIGN_INITIALIZATION) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (KYORI_GSON_SERIALIZER != null) {
|
||||||
|
return true; // already initialized
|
||||||
|
}
|
||||||
if (!PaperLib.isPaper()) {
|
if (!PaperLib.isPaper()) {
|
||||||
if (!PAPER_SIGN_NOTIFIED) {
|
if (!PAPER_SIGN_NOTIFIED) {
|
||||||
PAPER_SIGN_NOTIFIED = true;
|
PAPER_SIGN_NOTIFIED = true;
|
||||||
@@ -241,19 +267,26 @@ public class StateWrapper {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final String[] dontRelocate = new String[]{"net.kyo" + "ri.adventure.text.serializer.gson.GsonComponentSerializer"};
|
char[] dontObfuscate = new char[]{
|
||||||
Class<?> gsonComponentSerializerClass = Class.forName(String.join("", dontRelocate));
|
'n', 'e', 't', '.', 'k', 'y', 'o', 'r', 'i', '.', 'a', 'd', 'v', 'e', 'n', 't', 'u', 'r', 'e', '.',
|
||||||
KYORI_GSON_SERIALIZER = Arrays.stream(gsonComponentSerializerClass.getMethods()).filter(method -> method
|
't', 'e', 'x', 't', '.', 's', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'e', 'r', '.', 'g', 's', 'o', 'n', '.',
|
||||||
.getName()
|
'G', 's', 'o', 'n', 'C', 'o', 'm', 'p', 'o', 'n', 'e', 'n', 't', 'S', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'e', 'r'
|
||||||
.equals("gson")).findFirst().orElseThrow().invoke(null);
|
};
|
||||||
|
Class<?> gsonComponentSerializerClass = Class.forName(new String(dontObfuscate));
|
||||||
|
LOGGER.info(gsonComponentSerializerClass);
|
||||||
|
KYORI_GSON_SERIALIZER = Arrays.stream(gsonComponentSerializerClass.getMethods())
|
||||||
|
.filter(method -> method.getName().equals("gson"))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow().invoke(null);
|
||||||
GSON_SERIALIZER_DESERIALIZE_TREE = LOOKUP.unreflect(Arrays
|
GSON_SERIALIZER_DESERIALIZE_TREE = LOOKUP.unreflect(Arrays
|
||||||
.stream(gsonComponentSerializerClass.getMethods())
|
.stream(gsonComponentSerializerClass.getMethods())
|
||||||
.filter(method -> method.getName().equals("deserializeFromTree") && method.getParameterCount() == 1)
|
.filter(method -> method.getName().equals("deserializeFromTree") && method.getParameterCount() == 1)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElseThrow());
|
.orElseThrow());
|
||||||
BUKKIT_SIGN_SIDE_LINE_SET = LOOKUP.unreflect(Arrays.stream(SignSide.class.getMethods()).filter(method -> method
|
BUKKIT_SIGN_SIDE_LINE_SET = LOOKUP.unreflect(Arrays.stream(SignSide.class.getMethods())
|
||||||
.getName()
|
.filter(method -> method.getName().equals("line") && method.getParameterCount() == 2)
|
||||||
.equals("line") && method.getParameterCount() == 2).findFirst().orElseThrow());
|
.findFirst()
|
||||||
|
.orElseThrow());
|
||||||
return true;
|
return true;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
FAILED_SIGN_INITIALIZATION = true;
|
FAILED_SIGN_INITIALIZATION = true;
|
||||||
@@ -336,15 +369,12 @@ public class StateWrapper {
|
|||||||
|
|
||||||
private static MethodHandle findCraftBlockEntityStateLoadDataMethodHandle(Class<?> craftBlockEntityStateClass) throws
|
private static MethodHandle findCraftBlockEntityStateLoadDataMethodHandle(Class<?> craftBlockEntityStateClass) throws
|
||||||
NoSuchMethodException, IllegalAccessException, ClassNotFoundException {
|
NoSuchMethodException, IllegalAccessException, ClassNotFoundException {
|
||||||
final Class<?> compoundTagClass = Class.forName("net.minecraft.nbt.CompoundTag"); // TODO: obfuscation...
|
|
||||||
for (final Method method : craftBlockEntityStateClass.getMethods()) {
|
for (final Method method : craftBlockEntityStateClass.getMethods()) {
|
||||||
if (method
|
if (method.getName().equals("loadData") && method.getParameterCount() == 1) {
|
||||||
.getReturnType()
|
|
||||||
.equals(Void.TYPE) && method.getParameterCount() == 1 && method.getParameterTypes()[0] == compoundTagClass) {
|
|
||||||
return LOOKUP.unreflect(method);
|
return LOOKUP.unreflect(method);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new NoSuchMethodException("Couldn't find method for component loading in " + compoundTagClass.getName());
|
throw new NoSuchMethodException("Couldn't find #loadData(CompoundTag) in " + craftBlockEntityStateClass.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MethodHandle findCraftBlockEntityStateUpdateMethodHandle(Class<?> craftBlockEntityStateClass) throws
|
private static MethodHandle findCraftBlockEntityStateUpdateMethodHandle(Class<?> craftBlockEntityStateClass) throws
|
||||||
|
Reference in New Issue
Block a user