diff --git a/api/src/main/java/net/momirealms/customcrops/common/util/ReflectionUtils.java b/api/src/main/java/net/momirealms/customcrops/common/util/ReflectionUtils.java new file mode 100644 index 0000000..7bf717c --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/common/util/ReflectionUtils.java @@ -0,0 +1,577 @@ +package net.momirealms.customcrops.common.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import sun.misc.Unsafe; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ReflectionUtils { + public static final Unsafe UNSAFE; + public static final MethodHandles.Lookup LOOKUP; + private static final MethodHandle methodHandle$MethodHandleNatives$refKindIsSetter; + private static final MethodHandle methodHandle$constructor$MemberName; + private static final MethodHandle methodHandle$MemberName$getReferenceKind; + private static final MethodHandle methodHandle$MethodHandles$Lookup$getDirectField; + + static { + try { + Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + UNSAFE = (Unsafe) unsafeField.get(null); + Field implLookup = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); + @SuppressWarnings("deprecation") Object base = UNSAFE.staticFieldBase(implLookup); + @SuppressWarnings("deprecation") long offset = UNSAFE.staticFieldOffset(implLookup); + LOOKUP = (MethodHandles.Lookup) UNSAFE.getObject(base, offset); // 获取神权lookup + Class clazz$MethodHandleNatives = Class.forName("java.lang.invoke.MethodHandleNatives"); + Class clazz$MemberName = Class.forName("java.lang.invoke.MemberName"); + methodHandle$MethodHandleNatives$refKindIsSetter = LOOKUP.unreflect(clazz$MethodHandleNatives.getDeclaredMethod("refKindIsSetter", byte.class)); + methodHandle$constructor$MemberName = LOOKUP.unreflectConstructor(clazz$MemberName.getDeclaredConstructor(Field.class, boolean.class)); + methodHandle$MemberName$getReferenceKind = LOOKUP.unreflect(clazz$MemberName.getDeclaredMethod("getReferenceKind")); + methodHandle$MethodHandles$Lookup$getDirectField = LOOKUP.unreflect(MethodHandles.Lookup.class.getDeclaredMethod("getDirectField", byte.class, Class.class, clazz$MemberName)); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private ReflectionUtils() {} + + public static Class getClazz(String... classes) { + for (String className : classes) { + Class clazz = getClazz(className); + if (clazz != null) { + return clazz; + } + } + return null; + } + + public static Class getClazz(String clazz) { + try { + return Class.forName(clazz); + } catch (Throwable e) { + return null; + } + } + + public static boolean classExists(@NotNull final String clazz) { + try { + Class.forName(clazz); + return true; + } catch (Throwable e) { + return false; + } + } + + public static boolean methodExists(@NotNull final Class clazz, @NotNull final String method, @NotNull final Class... parameterTypes) { + try { + clazz.getMethod(method, parameterTypes); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + @Nullable + public static Field getDeclaredField(final Class clazz, final String field) { + try { + return setAccessible(clazz.getDeclaredField(field)); + } catch (NoSuchFieldException e) { + return null; + } + } + + @Nullable + public static Field getDeclaredField(@NotNull Class clazz, @NotNull String... possibleNames) { + List possibleNameList = Arrays.asList(possibleNames); + for (Field field : clazz.getDeclaredFields()) { + if (possibleNameList.contains(field.getName())) { + return field; + } + } + return null; + } + + @Nullable + public static Field getDeclaredField(final Class clazz, final int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (index == i) { + return setAccessible(field); + } + i++; + } + return null; + } + + @Nullable + public static Field getInstanceDeclaredField(final Class clazz, final int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers())) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @Nullable + public static Field getStaticDeclaredField(final Class clazz, final Class type, final int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (field.getType() == type) { + if (Modifier.isStatic(field.getModifiers())) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + } + return null; + } + + @Nullable + public static Field getDeclaredField(final Class clazz, final Class type, int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (field.getType() == type) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @Nullable + public static Field getDeclaredFieldBackwards(final Class clazz, final Class type, int index) { + int i = 0; + Field[] fields = clazz.getDeclaredFields(); + for (int j = fields.length - 1; j >= 0; j--) { + Field field = fields[j]; + if (field.getType() == type) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @Nullable + public static Field getInstanceDeclaredField(@NotNull Class clazz, final Class type, int index) { + int i = 0; + for (final Field field : clazz.getDeclaredFields()) { + if (field.getType() == type && !Modifier.isStatic(field.getModifiers())) { + if (index == i) { + return setAccessible(field); + } + i++; + } + } + return null; + } + + @NotNull + public static List getDeclaredFields(final Class clazz) { + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + fields.add(setAccessible(field)); + } + return fields; + } + + @NotNull + public static List getInstanceDeclaredFields(@NotNull Class clazz) { + List list = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers())) { + list.add(setAccessible(field)); + } + } + return list; + } + + @NotNull + public static List getDeclaredFields(@NotNull final Class clazz, @NotNull final Class type) { + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.getType() == type) { + fields.add(setAccessible(field)); + } + } + return fields; + } + + @NotNull + public static List getInstanceDeclaredFields(@NotNull Class clazz, @NotNull Class type) { + List list = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.getType() == type && !Modifier.isStatic(field.getModifiers())) { + list.add(setAccessible(field)); + } + } + return list; + } + + @Nullable + public static Method getMethod(final Class clazz, Class returnType, final String[] possibleMethodNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + for (String name : possibleMethodNames) { + if (name.equals(method.getName())) { + if (returnType.isAssignableFrom(method.getReturnType())) { + return method; + } + } + } + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, final String[] possibleMethodNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + for (String name : possibleMethodNames) { + if (name.equals(method.getName())) return method; + } + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, Class returnType, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) return method; + } + return null; + } + + @Nullable + public static Method getInstanceMethod(final Class clazz, Class returnType, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + if (Modifier.isStatic(method.getModifiers())) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) return method; + } + return null; + } + + @Nullable + public static Method getDeclaredMethod(final Class clazz, Class returnType, final String[] possibleMethodNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getDeclaredMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + for (String name : possibleMethodNames) { + if (name.equals(method.getName())) { + if (returnType.isAssignableFrom(method.getReturnType())) { + return setAccessible(method); + } + } + } + } + return null; + } + + @Nullable + public static Method getDeclaredMethod(final Class clazz, Class returnType, final Class... parameterTypes) { + outer: + for (Method method : clazz.getDeclaredMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) return setAccessible(method); + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, Class returnType, int index) { + int i = 0; + for (Method method : clazz.getMethods()) { + if (returnType.isAssignableFrom(method.getReturnType())) { + if (i == index) { + return setAccessible(method); + } + i++; + } + } + return null; + } + + @Nullable + public static Method getStaticMethod(final Class clazz, Class returnType, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + if (!Modifier.isStatic(method.getModifiers())) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) + return setAccessible(method); + } + return null; + } + + @Nullable + public static Method getStaticMethod(final Class clazz, Class returnType, String[] possibleNames, final Class... parameterTypes) { + outer: + for (Method method : clazz.getMethods()) { + if (method.getParameterCount() != parameterTypes.length) { + continue; + } + if (!Modifier.isStatic(method.getModifiers())) { + continue; + } + Class[] types = method.getParameterTypes(); + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + continue outer; + } + } + if (returnType.isAssignableFrom(method.getReturnType())) { + for (String name : possibleNames) { + if (name.equals(method.getName())) { + return setAccessible(method); + } + } + } + } + return null; + } + + public static Method getStaticMethod(final Class clazz, int index) { + int i = 0; + for (Method method : clazz.getMethods()) { + if (Modifier.isStatic(method.getModifiers())) { + if (i == index) { + return setAccessible(method); + } + i++; + } + } + return null; + } + + @Nullable + public static Method getMethod(final Class clazz, int index) { + int i = 0; + for (Method method : clazz.getMethods()) { + if (i == index) { + return setAccessible(method); + } + i++; + } + return null; + } + + public static Method getMethodOrElseThrow(final Class clazz, final String[] possibleMethodNames, final Class[] parameterTypes) throws NoSuchMethodException { + Method method = getMethod(clazz, possibleMethodNames, parameterTypes); + if (method == null) { + throw new NoSuchMethodException("No method found with possible names " + Arrays.toString(possibleMethodNames) + " with parameters " + + Arrays.toString(parameterTypes) + " in class " + clazz.getName()); + } + return method; + } + + @NotNull + public static List getMethods(@NotNull Class clazz, @NotNull Class returnType, @NotNull Class... parameterTypes) { + List list = new ArrayList<>(); + for (Method method : clazz.getMethods()) { + if (!returnType.isAssignableFrom(method.getReturnType()) // check type + || method.getParameterCount() != parameterTypes.length // check length + ) continue; + Class[] types = method.getParameterTypes(); + outer: { + for (int i = 0; i < types.length; i++) { + if (types[i] != parameterTypes[i]) { + break outer; + } + } + list.add(method); + } + } + return list; + } + + @NotNull + public static T setAccessible(@NotNull final T o) { + o.setAccessible(true); + return o; + } + + @Nullable + public static Constructor getConstructor(Class clazz, Class... parameterTypes) { + try { + return clazz.getConstructor(parameterTypes); + } catch (NoSuchMethodException | SecurityException ignore) { + return null; + } + } + + @Nullable + public static Constructor getDeclaredConstructor(Class clazz, Class... parameterTypes) { + try { + return setAccessible(clazz.getDeclaredConstructor(parameterTypes)); + } catch (NoSuchMethodException | SecurityException ignore) { + return null; + } + } + + @Nullable + public static Constructor getConstructor(Class clazz, int index) { + try { + Constructor[] constructors = clazz.getDeclaredConstructors(); + if (index < 0 || index >= constructors.length) { + throw new IndexOutOfBoundsException("Invalid constructor index: " + index); + } + return setAccessible(constructors[index]); + } catch (SecurityException e) { + return null; + } + } + + @NotNull + public static Constructor getTheOnlyConstructor(Class clazz) { + Constructor[] constructors = clazz.getConstructors(); + if (constructors.length != 1) { + throw new RuntimeException("This class is expected to have only one constructor but it has " + constructors.length); + } + return constructors[0]; + } + + public static MethodHandle unreflectGetter(Field field) throws IllegalAccessException { + try { + return LOOKUP.unreflectGetter(field); + } catch (IllegalAccessException e) { + field.setAccessible(true); + return LOOKUP.unreflectGetter(field); + } + } + + @Nullable + public static MethodHandle unreflectSetter(Field field) { + try { + return LOOKUP.unreflectSetter(field); + } catch (IllegalAccessException e) { + try { // 绕过final限制获取方法句柄 + Object memberName = methodHandle$constructor$MemberName.invoke(field, true); + Object refKind = methodHandle$MemberName$getReferenceKind.invoke(memberName); + methodHandle$MethodHandleNatives$refKindIsSetter.invoke(refKind); + return (MethodHandle) methodHandle$MethodHandles$Lookup$getDirectField.invoke(LOOKUP, refKind, field.getDeclaringClass(), memberName); + } catch (Throwable ex) { + return null; + } + } + } + + public static MethodHandle unreflectMethod(Method method) throws IllegalAccessException { + try { + return LOOKUP.unreflect(method); + } catch (IllegalAccessException e) { + method.setAccessible(true); + return LOOKUP.unreflect(method); + } + } + + public static MethodHandle unreflectConstructor(Constructor constructor) throws IllegalAccessException { + try { + return LOOKUP.unreflectConstructor(constructor); + } catch (IllegalAccessException e) { + constructor.setAccessible(true); + return LOOKUP.unreflectConstructor(constructor); + } + } + + public static VarHandle findVarHandle(Class clazz, String name, Class type) { + try { + return MethodHandles.privateLookupIn(clazz, LOOKUP) + .findVarHandle(clazz, name, type); + } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) { + return null; + } + } + + public static VarHandle findVarHandle(Field field) { + try { + return MethodHandles.privateLookupIn(field.getDeclaringClass(), LOOKUP) + .findVarHandle(field.getDeclaringClass(), field.getName(), field.getType()); + } catch (IllegalAccessException | NoSuchFieldException e) { + return null; + } + } +} diff --git a/compatibility-asp-r2/build.gradle.kts b/compatibility-asp-r2/build.gradle.kts index ea63323..6d5e920 100644 --- a/compatibility-asp-r2/build.gradle.kts +++ b/compatibility-asp-r2/build.gradle.kts @@ -7,7 +7,9 @@ repositories { dependencies { compileOnly(project(":api")) + compileOnly("dev.folia:folia-api:1.20.4-R0.1-SNAPSHOT") compileOnly("com.infernalsuite.asp:api:4.0.0-SNAPSHOT") + compileOnly("com.flowpowered:flow-nbt:${rootProject.properties["flow_nbt_version"]}") } java { diff --git a/compatibility-asp-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/asp_r2/SlimeWorldAdaptorR2.java b/compatibility-asp-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/asp_r2/SlimeWorldAdaptorR2.java index 5d35ed2..11d3ec8 100644 --- a/compatibility-asp-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/asp_r2/SlimeWorldAdaptorR2.java +++ b/compatibility-asp-r2/src/main/java/net/momirealms/customcrops/bukkit/integration/adaptor/asp_r2/SlimeWorldAdaptorR2.java @@ -17,8 +17,13 @@ package net.momirealms.customcrops.bukkit.integration.adaptor.asp_r2; -import com.infernalsuite.aswm.api.events.LoadSlimeWorldEvent; -import com.infernalsuite.aswm.api.world.SlimeWorld; +import com.flowpowered.nbt.*; +import com.flowpowered.nbt.stream.NBTInputStream; +import com.flowpowered.nbt.stream.NBTOutputStream; +import com.infernalsuite.asp.api.AdvancedSlimePaperAPI; +import com.infernalsuite.asp.api.events.LoadSlimeWorldEvent; +import com.infernalsuite.asp.api.world.SlimeChunk; +import com.infernalsuite.asp.api.world.SlimeWorld; import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; import net.momirealms.customcrops.api.core.InternalRegistries; import net.momirealms.customcrops.api.core.block.CustomCropsBlock; @@ -28,14 +33,19 @@ import net.momirealms.customcrops.api.util.StringUtils; import net.momirealms.customcrops.api.util.TagUtils; import net.momirealms.customcrops.common.helper.GsonHelper; import net.momirealms.customcrops.common.util.Key; +import net.momirealms.customcrops.common.util.ReflectionUtils; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.Nullable; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.Method; +import java.nio.ByteOrder; +import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Optional; @@ -44,38 +54,26 @@ import java.util.concurrent.PriorityBlockingQueue; import java.util.function.Function; public class SlimeWorldAdaptorR2 extends AbstractWorldAdaptor implements Listener { + private final Class byteArrayTagClass = ReflectionUtils.getClazz("net{}kyori{}adventure{}nbt{}ByteArrayBinaryTag".replace("{}", ".")); + private final Method method$ByteArrayBinaryTag$byteArrayBinaryTag = ReflectionUtils.getStaticMethod(byteArrayTagClass, byteArrayTagClass, byte.class.arrayType()); + private final Method method$ByteArrayBinaryTag$value = ReflectionUtils.getMethod(byteArrayTagClass, byte.class.arrayType()); - private final Function getSlimeWorldFunction; + public SlimeWorldAdaptorR2() { + } - public SlimeWorldAdaptorR2(int version) { + public byte[] byteArrayTagToBytes(Object byteArrayTag) { try { - if (version == 1) { - Plugin plugin = Bukkit.getPluginManager().getPlugin("SlimeWorldManager"); - Class slimeClass = Class.forName("com.infernalsuite.aswm.api.SlimePlugin"); - Method method = slimeClass.getMethod("getWorld", String.class); - this.getSlimeWorldFunction = (name) -> { - try { - return (SlimeWorld) method.invoke(plugin, name); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - }; - } else if (version == 2) { - Class apiClass = Class.forName("com.infernalsuite.aswm.api.AdvancedSlimePaperAPI"); - Object apiInstance = apiClass.getMethod("instance").invoke(null); - Method method = apiClass.getMethod("getLoadedWorld", String.class); - this.getSlimeWorldFunction = (name) -> { - try { - return (SlimeWorld) method.invoke(apiInstance, name); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - }; - } else { - throw new IllegalArgumentException("Unsupported version: " + version); - } + return (byte[]) method$ByteArrayBinaryTag$value.invoke(byteArrayTag); } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); + throw new RuntimeException("Failed to convert byte array tag to byte[]", e); + } + } + + public Object bytesToByteArrayTag(byte[] bytes) { + try { + return method$ByteArrayBinaryTag$byteArrayBinaryTag.invoke(null, (Object) bytes); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to convert byte array tag to byte[]", e); } } @@ -93,32 +91,45 @@ public class SlimeWorldAdaptorR2 extends AbstractWorldAdaptor implem @Override public SlimeWorld getWorld(String worldName) { - return getSlimeWorldFunction.apply(worldName); - } - - private CompoundMap createOrGetDataMap(SlimeWorld world) { - Optional optionalCompoundTag = world.getExtraData().getAsCompoundTag("customcrops"); - CompoundMap ccDataMap; - if (optionalCompoundTag.isEmpty()) { - ccDataMap = new CompoundMap(); - world.getExtraData().getValue().put(new CompoundTag("customcrops", ccDataMap)); - } else { - ccDataMap = optionalCompoundTag.get().getValue(); - } - return ccDataMap; + return AdvancedSlimePaperAPI.instance().getLoadedWorld(worldName); } @Override public WorldExtraData loadExtraData(SlimeWorld world) { - CompoundMap ccDataMap = createOrGetDataMap(world); + Object extraTag = world.getExtraData().get("customcrops-extra-data"); + CompoundMap ccDataMap = null; + if (extraTag != null) { + try { + NBTInputStream nbtInputStream = new NBTInputStream(new ByteArrayInputStream(byteArrayTagToBytes(extraTag)), 0, ByteOrder.BIG_ENDIAN); + ccDataMap = ((CompoundTag) nbtInputStream.readTag()).getValue(); + } catch (IOException e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to read extra data from custom crops tag", e); + } + } + if (ccDataMap == null) { + ccDataMap = new CompoundMap(); + } String json = Optional.ofNullable(ccDataMap.get("world-info")).map(tag -> tag.getAsStringTag().get().getValue()).orElse(null); return (json == null || json.equals("null")) ? WorldExtraData.empty() : GsonHelper.get().fromJson(json, WorldExtraData.class); } + @SuppressWarnings({"unchecked", "rawtypes"}) @Override public void saveExtraData(CustomCropsWorld world) { - CompoundMap ccDataMap = createOrGetDataMap(world.world()); - ccDataMap.put(new StringTag("world-info", GsonHelper.get().toJson(world.extraData()))); + CompoundTag ccDataMap = new CompoundTag("", new CompoundMap()); + ccDataMap.getValue().put(new StringTag("world-info", GsonHelper.get().toJson(world.extraData()))); + byte[] data; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + NBTOutputStream nbtOutputStream = new NBTOutputStream(baos, 0, ByteOrder.BIG_ENDIAN); + nbtOutputStream.writeTag(ccDataMap); + data = baos.toByteArray(); + nbtOutputStream.close(); + Map data2 = (Map) world.world().getExtraData(); + data2.put("customcrops-extra-data", bytesToByteArrayTag(data)); + } catch (IOException e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to save extra data to custom crops tag", e); + } } @Nullable @@ -131,16 +142,24 @@ public class SlimeWorldAdaptorR2 extends AbstractWorldAdaptor implem @Override public CustomCropsChunk loadChunk(CustomCropsWorld world, ChunkPos pos, boolean createIfNotExists) { long time1 = System.currentTimeMillis(); - CompoundMap ccDataMap = createOrGetDataMap(world.world()); - Tag chunkTag = ccDataMap.get(pos.asString()); + SlimeChunk slimeChunk = world.world().getChunk(pos.x(), pos.z()); + if (slimeChunk == null) { + return createIfNotExists ? world.createChunk(pos) : null; + } + Object extraTag = slimeChunk.getExtraData().get("customcrops-data"); + CompoundTag chunkTag = null; + if (extraTag != null) { + try { + NBTInputStream nbtInputStream = new NBTInputStream(new ByteArrayInputStream(byteArrayTagToBytes(extraTag)), 0, ByteOrder.BIG_ENDIAN); + chunkTag = ((CompoundTag) nbtInputStream.readTag()); + } catch (IOException e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to read extra data from custom crops tag", e); + } + } if (chunkTag == null) { return createIfNotExists ? world.createChunk(pos) : null; } - Optional chunkCompoundTag = chunkTag.getAsCompoundTag(); - if (chunkCompoundTag.isEmpty()) { - return createIfNotExists ? world.createChunk(pos) : null; - } - CustomCropsChunk chunk = tagToChunk(world, chunkCompoundTag.get()); + CustomCropsChunk chunk = tagToChunk(world, chunkTag); long time2 = System.currentTimeMillis(); BukkitCustomCropsPlugin.getInstance().debug(() -> "Took " + (time2-time1) + "ms to load chunk " + pos); return chunk; @@ -149,15 +168,32 @@ public class SlimeWorldAdaptorR2 extends AbstractWorldAdaptor implem @Override public void saveRegion(CustomCropsWorld world, CustomCropsRegion region) {} + @SuppressWarnings({"unchecked", "rawtypes"}) @Override public void saveChunk(CustomCropsWorld world, CustomCropsChunk chunk) { - CompoundMap ccDataMap = createOrGetDataMap(world.world()); + ChunkPos chunkPos = chunk.chunkPos(); + SlimeChunk slimeChunk = world.world().getChunk(chunkPos.x(), chunkPos.z()); + if (slimeChunk == null) { + return; + } + Map data = (Map) slimeChunk.getExtraData(); SerializableChunk serializableChunk = toSerializableChunk(chunk); Runnable runnable = () -> { if (serializableChunk.canPrune()) { - ccDataMap.remove(chunk.chunkPos().asString()); + data.remove("customcrops-data"); } else { - ccDataMap.put(chunkToTag(serializableChunk)); + CompoundTag chunkTag = chunkToTag(serializableChunk); + byte[] bytes; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + NBTOutputStream nbtOutputStream = new NBTOutputStream(baos, 0, ByteOrder.BIG_ENDIAN); + nbtOutputStream.writeTag(chunkTag); + bytes = baos.toByteArray(); + nbtOutputStream.close(); + data.put("customcrops-data", bytesToByteArrayTag(bytes)); + } catch (IOException e) { + BukkitCustomCropsPlugin.getInstance().getPluginLogger().severe("Failed to save chunk " + chunk.chunkPos() + " on world " + world.worldName(), e); + } } }; if (Bukkit.isPrimaryThread()) { diff --git a/gradle.properties b/gradle.properties index 0c24292..0357c49 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=3.6.46.1 +project_version=3.6.47 config_version=43 project_group=net.momirealms diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 669fdf1..97e02d0 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { } implementation(project(":compatibility")) implementation(project(":compatibility-asp-r1")) -// implementation(project(":compatibility-asp-r2")) + implementation(project(":compatibility-asp-r2")) implementation("net.kyori:adventure-api:${rootProject.properties["adventure_bundle_version"]}") implementation("net.kyori:adventure-text-minimessage:${rootProject.properties["adventure_bundle_version"]}") diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java index 05ab6f9..5efbe51 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/world/BukkitWorldManager.java @@ -28,6 +28,7 @@ import net.momirealms.customcrops.api.integration.SeasonProvider; import net.momirealms.customcrops.bukkit.config.BukkitConfigManager; import net.momirealms.customcrops.bukkit.integration.adaptor.BukkitWorldAdaptor; import net.momirealms.customcrops.bukkit.integration.adaptor.asp_r1.SlimeWorldAdaptorR1; +import net.momirealms.customcrops.bukkit.integration.adaptor.asp_r2.SlimeWorldAdaptorR2; import net.momirealms.customcrops.common.helper.VersionHelper; import org.bukkit.Bukkit; import org.bukkit.Chunk; @@ -59,17 +60,22 @@ public class BukkitWorldManager implements WorldManager, Listener { try { Class.forName("com.infernalsuite.aswm.api.SlimePlugin"); SlimeWorldAdaptorR1 adaptor = new SlimeWorldAdaptorR1(1); - adaptors.add(adaptor); + this.adaptors.add(adaptor); Bukkit.getPluginManager().registerEvents(adaptor, plugin.getBootstrap()); plugin.getPluginLogger().info("SlimeWorldManager hooked!"); } catch (ClassNotFoundException ignored) { } if (Bukkit.getPluginManager().isPluginEnabled("SlimeWorldPlugin")) { SlimeWorldAdaptorR1 adaptor = new SlimeWorldAdaptorR1(2); - adaptors.add(adaptor); + this.adaptors.add(adaptor); Bukkit.getPluginManager().registerEvents(adaptor, plugin.getBootstrap()); plugin.getPluginLogger().info("AdvancedSlimePaper hooked!"); } + } else { + SlimeWorldAdaptorR2 adaptor = new SlimeWorldAdaptorR2(); + this.adaptors.add(adaptor); + Bukkit.getPluginManager().registerEvents(adaptor, plugin.getBootstrap()); + plugin.getPluginLogger().info("AdvancedSlimePaper hooked!"); } this.adaptors.add(new BukkitWorldAdaptor()); this.seasonProvider = new SeasonProvider() { @@ -369,7 +375,7 @@ public class BukkitWorldManager implements WorldManager, Listener { @Override public CustomCropsWorld adapt(String name) { - for (WorldAdaptor adaptor : adaptors) { + for (WorldAdaptor adaptor : this.adaptors) { Object world = adaptor.getWorld(name); if (world != null) { return adaptor.adapt(world); diff --git a/settings.gradle.kts b/settings.gradle.kts index 0f77460..dd3adf2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,6 +4,7 @@ include(":plugin") include(":plugin:j21") include(":compatibility") include(":compatibility-asp-r1") +include(":compatibility-asp-r2") //include(":compatibility-asp-r2") include(":compatibility-oraxen-r1") include(":compatibility-oraxen-r2")