diff --git a/.github/workflows/buildtools.sh b/.github/workflows/buildtools.sh index 13cad4c..c5da7e3 100644 --- a/.github/workflows/buildtools.sh +++ b/.github/workflows/buildtools.sh @@ -35,4 +35,5 @@ checkVersion "1.20.6" "21" checkVersion "1.21" "21" checkVersion "1.21.3" "21" checkVersion "1.21.4" "21" -checkVersion "1.21.5" "21" \ No newline at end of file +checkVersion "1.21.5" "21" +checkVersion "1.21.6" "21" \ No newline at end of file diff --git a/zip-api/src/main/java/net/imprex/zip/api/ZIPHandler.java b/zip-api/src/main/java/net/imprex/zip/api/ZIPHandler.java index f29e5fd..be4c32f 100644 --- a/zip-api/src/main/java/net/imprex/zip/api/ZIPHandler.java +++ b/zip-api/src/main/java/net/imprex/zip/api/ZIPHandler.java @@ -16,4 +16,6 @@ public interface ZIPHandler { ZIPBackpackType getBackpackType(ItemStack item); boolean isBackpack(ItemStack item); + + ZIPUniqueId getUniqueId(ItemStack item); } diff --git a/zip-common/src/main/java/net/imprex/zip/common/BPConstants.java b/zip-common/src/main/java/net/imprex/zip/common/BPConstants.java new file mode 100644 index 0000000..a7f1cc1 --- /dev/null +++ b/zip-common/src/main/java/net/imprex/zip/common/BPConstants.java @@ -0,0 +1,19 @@ +package net.imprex.zip.common; + +public class BPConstants { + + public static final int VERSION = 2; + public static final int INVENTORY_VERSION = 2; + + public static final String KEY_VERSION = "version"; + + public static final String KEY_ID = "id"; + public static final String KEY_TYPE_RAW = "typeRaw"; + public static final String KEY_INVENTORY = "inventory"; + + public static final String KEY_INVENTORY_VERSION = "version"; + public static final String KEY_INVENTORY_DATA_VERSION = "dataVersion"; + public static final String KEY_INVENTORY_ITEMS = "items"; + public static final String KEY_INVENTORY_ITEMS_SIZE = "itemsSize"; + public static final String KEY_INVENTORY_SLOT = "ZIPslot"; +} diff --git a/zip-common/src/main/java/net/imprex/zip/common/MinecraftVersion.java b/zip-common/src/main/java/net/imprex/zip/common/MinecraftVersion.java index 3e3cfb9..408c70f 100644 --- a/zip-common/src/main/java/net/imprex/zip/common/MinecraftVersion.java +++ b/zip-common/src/main/java/net/imprex/zip/common/MinecraftVersion.java @@ -1,24 +1,24 @@ +/** + * @author Imprex-Development + * @see MinecraftVersion.java + */ package net.imprex.zip.common; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bukkit.Bukkit; -/** - * @author Imprex-Development - * @see https://github.com/Imprex-Development/orebfuscator/blob/master/orebfuscator-common/src/main/java/net/imprex/orebfuscator/util/MinecraftVersion.java - */ -public final class MinecraftVersion implements Comparable { +public final class MinecraftVersion { private static final class NmsMapping { private static final List MAPPINGS = new ArrayList<>(); static { + MAPPINGS.add(new NmsMapping("1.21.6", "v1_21_R5")); MAPPINGS.add(new NmsMapping("1.21.5", "v1_21_R4")); MAPPINGS.add(new NmsMapping("1.21.4", "v1_21_R3")); MAPPINGS.add(new NmsMapping("1.21.3", "v1_21_R2")); @@ -26,11 +26,11 @@ private static final class NmsMapping { MAPPINGS.add(new NmsMapping("1.20.5", "v1_20_R4")); } - public static String get(MinecraftVersion version) { + public static String get(Version version) { for (NmsMapping mapping : MAPPINGS) { if (version.isAtOrAbove(mapping.version)) { if (mapping.version.minor() != version.minor()) { - System.out.println(String.format("Using nms mapping with mismatched minor versions: %s - %s", + ZIPLogger.warn(String.format("Using nms mapping with mismatched minor versions: %s - %s", mapping.version, version)); } @@ -41,19 +41,17 @@ public static String get(MinecraftVersion version) { throw new RuntimeException("Can't get nms package version for minecraft version: " + version); } - private final MinecraftVersion version; + private final Version version; private final String nmsVersion; public NmsMapping(String version, String nmsVersion) { - this.version = new MinecraftVersion(version); + this.version = Version.parse(version); this.nmsVersion = nmsVersion; } } - private static final Pattern VERSION_PATTERN = Pattern.compile("(?\\d+)(?:\\.(?\\d+))(?:\\.(?\\d+))?"); private static final Pattern PACKAGE_PATTERN = Pattern.compile("org\\.bukkit\\.craftbukkit\\.(v\\d+_\\d+_R\\d+)"); - - private static final MinecraftVersion CURRENT_VERSION = new MinecraftVersion(Bukkit.getBukkitVersion()); + private static final Version CURRENT_VERSION = Version.parse(Bukkit.getBukkitVersion()); private static String NMS_VERSION; @@ -72,118 +70,35 @@ public static String nmsVersion() { return NMS_VERSION; } + public static Version current() { + return CURRENT_VERSION; + } + public static int majorVersion() { - return CURRENT_VERSION.major; + return CURRENT_VERSION.major(); } public static int minorVersion() { - return CURRENT_VERSION.minor; + return CURRENT_VERSION.minor(); } public static int patchVersion() { - return CURRENT_VERSION.patch; + return CURRENT_VERSION.patch(); } public static boolean isAbove(String version) { - return CURRENT_VERSION.isAbove(new MinecraftVersion(version)); + return CURRENT_VERSION.isAbove(Version.parse(version)); } public static boolean isAtOrAbove(String version) { - return CURRENT_VERSION.isAtOrAbove(new MinecraftVersion(version)); + return CURRENT_VERSION.isAtOrAbove(Version.parse(version)); } public static boolean isAtOrBelow(String version) { - return CURRENT_VERSION.isAtOrBelow(new MinecraftVersion(version)); + return CURRENT_VERSION.isAtOrBelow(Version.parse(version)); } public static boolean isBelow(String version) { - return CURRENT_VERSION.isBelow(new MinecraftVersion(version)); - } - - private final int major; - private final int minor; - private final int patch; - - public MinecraftVersion(String version) { - Matcher matcher = VERSION_PATTERN.matcher(version); - - if (!matcher.find()) { - throw new IllegalArgumentException("can't parse minecraft version: " + version); - } - - this.major = Integer.parseInt(matcher.group("major")); - this.minor = Integer.parseInt(matcher.group("minor")); - - String patch = matcher.group("patch"); - if (patch != null) { - this.patch = Integer.parseInt(patch); - } else { - this.patch = 0; - } - } - - public int major() { - return this.major; - } - - public int minor() { - return this.minor; - } - - public int patch() { - return this.patch; - } - - public boolean isAbove(MinecraftVersion version) { - return this.compareTo(version) > 0; - } - - public boolean isAtOrAbove(MinecraftVersion version) { - return this.compareTo(version) >= 0; - } - - public boolean isAtOrBelow(MinecraftVersion version) { - return this.compareTo(version) <= 0; - } - - public boolean isBelow(MinecraftVersion version) { - return this.compareTo(version) < 0; - } - - @Override - public int compareTo(MinecraftVersion other) { - int major = Integer.compare(this.major, other.major); - if (major != 0) { - return major; - } - - int minor = Integer.compare(this.minor, other.minor); - if (minor != 0) { - return minor; - } - - return Integer.compare(this.patch, other.patch); - } - - @Override - public int hashCode() { - return Objects.hash(major, minor, patch); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof MinecraftVersion)) { - return false; - } - MinecraftVersion other = (MinecraftVersion) obj; - return major == other.major && minor == other.minor && patch == other.patch; - } - - @Override - public String toString() { - return String.format("%s.%s.%s", this.major, this.minor, this.patch); + return CURRENT_VERSION.isBelow(Version.parse(version)); } } \ No newline at end of file diff --git a/zip-common/src/main/java/net/imprex/zip/common/Version.java b/zip-common/src/main/java/net/imprex/zip/common/Version.java new file mode 100644 index 0000000..729f905 --- /dev/null +++ b/zip-common/src/main/java/net/imprex/zip/common/Version.java @@ -0,0 +1,107 @@ +/** + * @author Imprex-Development + * @see Version.java + */ +package net.imprex.zip.common; + +import java.io.IOException; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +public record Version(int major, int minor, int patch) implements Comparable { + + private static final Pattern VERSION_PATTERN = Pattern.compile("(?\\d+)(?:\\.(?\\d+))?(?:\\.(?\\d+))?"); + + public static Version parse(String version) { + Matcher matcher = VERSION_PATTERN.matcher(version); + + if (!matcher.find()) { + throw new IllegalArgumentException("can't parse version: " + version); + } + + int major = Integer.parseInt(matcher.group("major")); + + String minorGroup = matcher.group("minor"); + int minor = minorGroup != null + ? Integer.parseInt(minorGroup) + : 0; + + String patchGroup = matcher.group("patch"); + int patch = patchGroup != null + ? Integer.parseInt(patchGroup) + : 0; + + return new Version(major, minor, patch); + } + + public boolean isAbove(Version version) { + return this.compareTo(version) > 0; + } + + public boolean isAtOrAbove(Version version) { + return this.compareTo(version) >= 0; + } + + public boolean isAtOrBelow(Version version) { + return this.compareTo(version) <= 0; + } + + public boolean isBelow(Version version) { + return this.compareTo(version) < 0; + } + + @Override + public int compareTo(Version other) { + int major = Integer.compare(this.major, other.major); + if (major != 0) { + return major; + } + + int minor = Integer.compare(this.minor, other.minor); + if (minor != 0) { + return minor; + } + + return Integer.compare(this.patch, other.patch); + } + + @Override + public int hashCode() { + return Objects.hash(major, minor, patch); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Version)) { + return false; + } + Version other = (Version) obj; + return major == other.major && minor == other.minor && patch == other.patch; + } + + @Override + public String toString() { + return String.format("%s.%s.%s", this.major, this.minor, this.patch); + } + + public static final class Json extends TypeAdapter { + + @Override + public void write(JsonWriter out, Version value) throws IOException { + out.value(value.toString()); + } + + @Override + public Version read(JsonReader in) throws IOException { + return Version.parse(in.nextString()); + } + } +} \ No newline at end of file diff --git a/zip-plugin/src/main/java/net/imprex/zip/util/ZIPLogger.java b/zip-common/src/main/java/net/imprex/zip/common/ZIPLogger.java similarity index 77% rename from zip-plugin/src/main/java/net/imprex/zip/util/ZIPLogger.java rename to zip-common/src/main/java/net/imprex/zip/common/ZIPLogger.java index 83fd3a5..9d8a693 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/util/ZIPLogger.java +++ b/zip-common/src/main/java/net/imprex/zip/common/ZIPLogger.java @@ -1,4 +1,4 @@ -package net.imprex.zip.util; +package net.imprex.zip.common; import java.util.logging.Level; import java.util.logging.Logger; @@ -16,6 +16,10 @@ public static void setVerbose(boolean verbose) { ZIPLogger.verbose = verbose; } + public static void log(Level level, String message) { + ZIPLogger.logger.log(level, LOG_PREFIX + message); + } + public static void debug(String message) { if (ZIPLogger.verbose) { ZIPLogger.logger.log(Level.FINE, LOG_DEBUG_PREFIX + message); @@ -30,6 +34,10 @@ public static void warn(String message) { ZIPLogger.logger.log(Level.WARNING, LOG_PREFIX + message); } + public static void warn(String message, Throwable throwable) { + ZIPLogger.logger.log(Level.WARNING, LOG_PREFIX + message, throwable); + } + public static void error(String message, Throwable throwable) { ZIPLogger.logger.log(Level.SEVERE, LOG_PREFIX + message, throwable); } diff --git a/zip-nms/pom.xml b/zip-nms/pom.xml index 2409ce8..a064cdc 100644 --- a/zip-nms/pom.xml +++ b/zip-nms/pom.xml @@ -23,5 +23,6 @@ zip-nms-v1_21_R2 zip-nms-v1_21_R3 zip-nms-v1_21_R4 + zip-nms-v1_21_R5 \ No newline at end of file diff --git a/zip-nms/zip-nms-api/src/main/java/net/imprex/zip/nms/api/ItemStackContainerResult.java b/zip-nms/zip-nms-api/src/main/java/net/imprex/zip/nms/api/ItemStackContainerResult.java new file mode 100644 index 0000000..3e5390d --- /dev/null +++ b/zip-nms/zip-nms-api/src/main/java/net/imprex/zip/nms/api/ItemStackContainerResult.java @@ -0,0 +1,6 @@ +package net.imprex.zip.nms.api; + +import java.util.List; + +public record ItemStackContainerResult(int containerSize, List items) { +} diff --git a/zip-nms/zip-nms-api/src/main/java/net/imprex/zip/nms/api/ItemStackWithSlot.java b/zip-nms/zip-nms-api/src/main/java/net/imprex/zip/nms/api/ItemStackWithSlot.java new file mode 100644 index 0000000..d1bdc29 --- /dev/null +++ b/zip-nms/zip-nms-api/src/main/java/net/imprex/zip/nms/api/ItemStackWithSlot.java @@ -0,0 +1,6 @@ +package net.imprex.zip.nms.api; + +import org.bukkit.inventory.ItemStack; + +public record ItemStackWithSlot(int slot, ItemStack item) { +} diff --git a/zip-nms/zip-nms-api/src/main/java/net/imprex/zip/nms/api/NmsManager.java b/zip-nms/zip-nms-api/src/main/java/net/imprex/zip/nms/api/NmsManager.java index 7bcaa4e..01c789d 100644 --- a/zip-nms/zip-nms-api/src/main/java/net/imprex/zip/nms/api/NmsManager.java +++ b/zip-nms/zip-nms-api/src/main/java/net/imprex/zip/nms/api/NmsManager.java @@ -1,16 +1,18 @@ package net.imprex.zip.nms.api; -import java.util.List; - import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonObject; + public interface NmsManager { - byte[] itemstackToBinary(ItemStack[] items); + JsonObject itemstackToJsonElement(ItemStack[] items); - List binaryToItemStack(byte[] binary); + ItemStackContainerResult jsonElementToItemStack(JsonObject jsonElement); + + JsonObject migrateToJsonElement(byte[] binary); void setSkullProfile(SkullMeta meta, String texture); diff --git a/zip-nms/zip-nms-v1_19_R1/src/main/java/net/imprex/zip/nms/v1_19_R1/ZipNmsManager.java b/zip-nms/zip-nms-v1_19_R1/src/main/java/net/imprex/zip/nms/v1_19_R1/ZipNmsManager.java index 2e1ebd5..36fae81 100644 --- a/zip-nms/zip-nms-v1_19_R1/src/main/java/net/imprex/zip/nms/v1_19_R1/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_19_R1/src/main/java/net/imprex/zip/nms/v1_19_R1/ZipNmsManager.java @@ -1,7 +1,7 @@ package net.imprex.zip.nms.v1_19_R1; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -12,15 +12,32 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; +import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.resources.RegistryOps; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; public class ZipNmsManager implements NmsManager { @@ -28,50 +45,113 @@ public class ZipNmsManager implements NmsManager { private static final Method CRAFTMETASKULL_SET_PROFILE = ReflectionUtil.getMethod(CRAFTMETASKULL_CLASS, "setProfile", GameProfile.class); - public byte[] nbtToBinary(CompoundTag compound) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - NbtIo.writeCompressed(compound, outputStream); - return outputStream.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - public CompoundTag binaryToNBT(byte[] binary) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { - return NbtIo.readCompressed(inputStream); - } catch (Exception e) { - e.printStackTrace(); + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + + private static final DynamicOps DYNAMIC_OPS_NBT = RegistryOps.create(NbtOps.INSTANCE, DEFAULT_REGISTRY); + private static final DynamicOps DYNAMIC_OPS_JSON = RegistryOps.create(JsonOps.INSTANCE, DEFAULT_REGISTRY); + + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); } - return new CompoundTag(); + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; } @Override - public byte[] itemstackToBinary(ItemStack[] items) { - CompoundTag inventory = new CompoundTag(); - ListTag list = new ListTag(); - for (ItemStack itemStack : items) { - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); - list.add(craftItem.save(new CompoundTag())); + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); + } + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(false, error -> {}); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); } - inventory.put("i", list); - return nbtToBinary(inventory); + + return new ItemStackContainerResult(itemsSize, items); } - + @Override - public List binaryToItemStack(byte[] binary) { - CompoundTag nbt = binaryToNBT(binary); - List items = new ArrayList<>(); - if (nbt.contains("i", 9)) { - ListTag list = nbt.getList("i", 10); - for (Tag base : list) { - if (base instanceof CompoundTag) { - items.add(CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.of((CompoundTag) base))); + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getList("i", 10); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id"); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(false, error -> {}); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; } } - return items; + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; } @Override diff --git a/zip-nms/zip-nms-v1_19_R2/src/main/java/net/imprex/zip/nms/v1_19_R2/ZipNmsManager.java b/zip-nms/zip-nms-v1_19_R2/src/main/java/net/imprex/zip/nms/v1_19_R2/ZipNmsManager.java index 4b80fb1..3410dfc 100644 --- a/zip-nms/zip-nms-v1_19_R2/src/main/java/net/imprex/zip/nms/v1_19_R2/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_19_R2/src/main/java/net/imprex/zip/nms/v1_19_R2/ZipNmsManager.java @@ -1,7 +1,7 @@ package net.imprex.zip.nms.v1_19_R2; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -12,15 +12,32 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; +import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.resources.RegistryOps; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; public class ZipNmsManager implements NmsManager { @@ -28,50 +45,113 @@ public class ZipNmsManager implements NmsManager { private static final Method CRAFTMETASKULL_SET_PROFILE = ReflectionUtil.getMethod(CRAFTMETASKULL_CLASS, "setProfile", GameProfile.class); - public byte[] nbtToBinary(CompoundTag compound) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - NbtIo.writeCompressed(compound, outputStream); - return outputStream.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - public CompoundTag binaryToNBT(byte[] binary) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { - return NbtIo.readCompressed(inputStream); - } catch (Exception e) { - e.printStackTrace(); + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + + private static final DynamicOps DYNAMIC_OPS_NBT = RegistryOps.create(NbtOps.INSTANCE, DEFAULT_REGISTRY); + private static final DynamicOps DYNAMIC_OPS_JSON = RegistryOps.create(JsonOps.INSTANCE, DEFAULT_REGISTRY); + + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); } - return new CompoundTag(); + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; } @Override - public byte[] itemstackToBinary(ItemStack[] items) { - CompoundTag inventory = new CompoundTag(); - ListTag list = new ListTag(); - for (ItemStack itemStack : items) { - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); - list.add(craftItem.save(new CompoundTag())); + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); + } + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(false, error -> {}); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); } - inventory.put("i", list); - return nbtToBinary(inventory); + + return new ItemStackContainerResult(itemsSize, items); } - + @Override - public List binaryToItemStack(byte[] binary) { - CompoundTag nbt = binaryToNBT(binary); - List items = new ArrayList<>(); - if (nbt.contains("i", 9)) { - ListTag list = nbt.getList("i", 10); - for (Tag base : list) { - if (base instanceof CompoundTag) { - items.add(CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.of((CompoundTag) base))); + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getList("i", 10); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id"); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(false, error -> {}); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; } } - return items; + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; } @Override diff --git a/zip-nms/zip-nms-v1_19_R3/src/main/java/net/imprex/zip/nms/v1_19_R3/ZipNmsManager.java b/zip-nms/zip-nms-v1_19_R3/src/main/java/net/imprex/zip/nms/v1_19_R3/ZipNmsManager.java index 33b2dd1..2afcf22 100644 --- a/zip-nms/zip-nms-v1_19_R3/src/main/java/net/imprex/zip/nms/v1_19_R3/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_19_R3/src/main/java/net/imprex/zip/nms/v1_19_R3/ZipNmsManager.java @@ -1,7 +1,7 @@ package net.imprex.zip.nms.v1_19_R3; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -12,15 +12,32 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; +import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.resources.RegistryOps; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; public class ZipNmsManager implements NmsManager { @@ -28,50 +45,113 @@ public class ZipNmsManager implements NmsManager { private static final Method CRAFTMETASKULL_SET_PROFILE = ReflectionUtil.getMethod(CRAFTMETASKULL_CLASS, "setProfile", GameProfile.class); - public byte[] nbtToBinary(CompoundTag compound) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - NbtIo.writeCompressed(compound, outputStream); - return outputStream.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - public CompoundTag binaryToNBT(byte[] binary) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { - return NbtIo.readCompressed(inputStream); - } catch (Exception e) { - e.printStackTrace(); + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + + private static final DynamicOps DYNAMIC_OPS_NBT = RegistryOps.create(NbtOps.INSTANCE, DEFAULT_REGISTRY); + private static final DynamicOps DYNAMIC_OPS_JSON = RegistryOps.create(JsonOps.INSTANCE, DEFAULT_REGISTRY); + + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); } - return new CompoundTag(); + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; } @Override - public byte[] itemstackToBinary(ItemStack[] items) { - CompoundTag inventory = new CompoundTag(); - ListTag list = new ListTag(); - for (ItemStack itemStack : items) { - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); - list.add(craftItem.save(new CompoundTag())); + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); + } + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(false, error -> {}); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); } - inventory.put("i", list); - return nbtToBinary(inventory); + + return new ItemStackContainerResult(itemsSize, items); } - + @Override - public List binaryToItemStack(byte[] binary) { - CompoundTag nbt = binaryToNBT(binary); - List items = new ArrayList<>(); - if (nbt.contains("i", 9)) { - ListTag list = nbt.getList("i", 10); - for (Tag base : list) { - if (base instanceof CompoundTag) { - items.add(CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.of((CompoundTag) base))); + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getList("i", 10); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id"); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(false, error -> {}); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; } } - return items; + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; } @Override diff --git a/zip-nms/zip-nms-v1_20_R1/src/main/java/net/imprex/zip/nms/v1_20_R1/ZipNmsManager.java b/zip-nms/zip-nms-v1_20_R1/src/main/java/net/imprex/zip/nms/v1_20_R1/ZipNmsManager.java index ecc1f92..5b72e6a 100644 --- a/zip-nms/zip-nms-v1_20_R1/src/main/java/net/imprex/zip/nms/v1_20_R1/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_20_R1/src/main/java/net/imprex/zip/nms/v1_20_R1/ZipNmsManager.java @@ -1,7 +1,7 @@ package net.imprex.zip.nms.v1_20_R1; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -12,15 +12,32 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; +import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.resources.RegistryOps; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; public class ZipNmsManager implements NmsManager { @@ -28,50 +45,113 @@ public class ZipNmsManager implements NmsManager { private static final Method CRAFTMETASKULL_SET_PROFILE = ReflectionUtil.getMethod(CRAFTMETASKULL_CLASS, "setProfile", GameProfile.class); - public byte[] nbtToBinary(CompoundTag compound) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - NbtIo.writeCompressed(compound, outputStream); - return outputStream.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - public CompoundTag binaryToNBT(byte[] binary) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { - return NbtIo.readCompressed(inputStream); - } catch (Exception e) { - e.printStackTrace(); + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + + private static final DynamicOps DYNAMIC_OPS_NBT = RegistryOps.create(NbtOps.INSTANCE, DEFAULT_REGISTRY); + private static final DynamicOps DYNAMIC_OPS_JSON = RegistryOps.create(JsonOps.INSTANCE, DEFAULT_REGISTRY); + + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); } - return new CompoundTag(); + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; } @Override - public byte[] itemstackToBinary(ItemStack[] items) { - CompoundTag inventory = new CompoundTag(); - ListTag list = new ListTag(); - for (ItemStack itemStack : items) { - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); - list.add(craftItem.save(new CompoundTag())); + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); + } + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(false, error -> {}); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); } - inventory.put("i", list); - return nbtToBinary(inventory); + + return new ItemStackContainerResult(itemsSize, items); } - + @Override - public List binaryToItemStack(byte[] binary) { - CompoundTag nbt = binaryToNBT(binary); - List items = new ArrayList<>(); - if (nbt.contains("i", 9)) { - ListTag list = nbt.getList("i", 10); - for (Tag base : list) { - if (base instanceof CompoundTag) { - items.add(CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.of((CompoundTag) base))); + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getList("i", 10); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id"); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(false, error -> {}); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; } } - return items; + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; } @Override diff --git a/zip-nms/zip-nms-v1_20_R2/src/main/java/net/imprex/zip/nms/v1_20_R2/ZipNmsManager.java b/zip-nms/zip-nms-v1_20_R2/src/main/java/net/imprex/zip/nms/v1_20_R2/ZipNmsManager.java index aa53f74..4b9eb75 100644 --- a/zip-nms/zip-nms-v1_20_R2/src/main/java/net/imprex/zip/nms/v1_20_R2/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_20_R2/src/main/java/net/imprex/zip/nms/v1_20_R2/ZipNmsManager.java @@ -1,7 +1,7 @@ package net.imprex.zip.nms.v1_20_R2; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -12,15 +12,32 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; +import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.resources.RegistryOps; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; public class ZipNmsManager implements NmsManager { @@ -28,50 +45,113 @@ public class ZipNmsManager implements NmsManager { private static final Method CRAFTMETASKULL_SET_PROFILE = ReflectionUtil.getMethod(CRAFTMETASKULL_CLASS, "setProfile", GameProfile.class); - public byte[] nbtToBinary(CompoundTag compound) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - NbtIo.writeCompressed(compound, outputStream); - return outputStream.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - public CompoundTag binaryToNBT(byte[] binary) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { - return NbtIo.readCompressed(inputStream); - } catch (Exception e) { - e.printStackTrace(); + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + + private static final DynamicOps DYNAMIC_OPS_NBT = RegistryOps.create(NbtOps.INSTANCE, DEFAULT_REGISTRY); + private static final DynamicOps DYNAMIC_OPS_JSON = RegistryOps.create(JsonOps.INSTANCE, DEFAULT_REGISTRY); + + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); } - return new CompoundTag(); + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; } @Override - public byte[] itemstackToBinary(ItemStack[] items) { - CompoundTag inventory = new CompoundTag(); - ListTag list = new ListTag(); - for (ItemStack itemStack : items) { - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); - list.add(craftItem.save(new CompoundTag())); + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); + } + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(false, error -> {}); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); } - inventory.put("i", list); - return nbtToBinary(inventory); + + return new ItemStackContainerResult(itemsSize, items); } - + @Override - public List binaryToItemStack(byte[] binary) { - CompoundTag nbt = binaryToNBT(binary); - List items = new ArrayList<>(); - if (nbt.contains("i", 9)) { - ListTag list = nbt.getList("i", 10); - for (Tag base : list) { - if (base instanceof CompoundTag) { - items.add(CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.of((CompoundTag) base))); + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getList("i", 10); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id"); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(false, error -> {}); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; } } - return items; + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; } @Override diff --git a/zip-nms/zip-nms-v1_20_R3/src/main/java/net/imprex/zip/nms/v1_20_R3/ZipNmsManager.java b/zip-nms/zip-nms-v1_20_R3/src/main/java/net/imprex/zip/nms/v1_20_R3/ZipNmsManager.java index 3cab8ee..ac7c775 100644 --- a/zip-nms/zip-nms-v1_20_R3/src/main/java/net/imprex/zip/nms/v1_20_R3/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_20_R3/src/main/java/net/imprex/zip/nms/v1_20_R3/ZipNmsManager.java @@ -1,7 +1,7 @@ package net.imprex.zip.nms.v1_20_R3; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -12,16 +12,33 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; +import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtAccounter; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.resources.RegistryOps; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; public class ZipNmsManager implements NmsManager { @@ -29,50 +46,113 @@ public class ZipNmsManager implements NmsManager { private static final Method CRAFTMETASKULL_SET_PROFILE = ReflectionUtil.getMethod(CRAFTMETASKULL_CLASS, "setProfile", GameProfile.class); - public byte[] nbtToBinary(CompoundTag compound) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - NbtIo.writeCompressed(compound, outputStream); - return outputStream.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - public CompoundTag binaryToNBT(byte[] binary) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { - return NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); - } catch (Exception e) { - e.printStackTrace(); + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + + private static final DynamicOps DYNAMIC_OPS_NBT = RegistryOps.create(NbtOps.INSTANCE, DEFAULT_REGISTRY); + private static final DynamicOps DYNAMIC_OPS_JSON = RegistryOps.create(JsonOps.INSTANCE, DEFAULT_REGISTRY); + + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); } - return new CompoundTag(); + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; } @Override - public byte[] itemstackToBinary(ItemStack[] items) { - CompoundTag inventory = new CompoundTag(); - ListTag list = new ListTag(); - for (ItemStack itemStack : items) { - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); - list.add(craftItem.save(new CompoundTag())); + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); + } + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(false, error -> {}); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); } - inventory.put("i", list); - return nbtToBinary(inventory); + + return new ItemStackContainerResult(itemsSize, items); } - + @Override - public List binaryToItemStack(byte[] binary) { - CompoundTag nbt = binaryToNBT(binary); - List items = new ArrayList<>(); - if (nbt.contains("i", 9)) { - ListTag list = nbt.getList("i", 10); - for (Tag base : list) { - if (base instanceof CompoundTag) { - items.add(CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.of((CompoundTag) base))); + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getList("i", 10); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id"); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(false, error -> {}); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow(false, error -> {}).getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; } } - return items; + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; } @Override diff --git a/zip-nms/zip-nms-v1_20_R4/src/main/java/net/imprex/zip/nms/v1_20_R4/ZipNmsManager.java b/zip-nms/zip-nms-v1_20_R4/src/main/java/net/imprex/zip/nms/v1_20_R4/ZipNmsManager.java index 73b2f24..b168ca8 100644 --- a/zip-nms/zip-nms-v1_20_R4/src/main/java/net/imprex/zip/nms/v1_20_R4/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_20_R4/src/main/java/net/imprex/zip/nms/v1_20_R4/ZipNmsManager.java @@ -1,30 +1,43 @@ package net.imprex.zip.nms.v1_20_R4; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.UUID; import org.bukkit.Material; -import org.bukkit.craftbukkit.v1_20_R4.CraftRegistry; import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtAccounter; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; public class ZipNmsManager implements NmsManager { @@ -32,70 +45,113 @@ public class ZipNmsManager implements NmsManager { private static final Method CRAFTMETASKULL_SET_PROFILE = ReflectionUtil.getMethod(CRAFTMETASKULL_CLASS, "setProfile", GameProfile.class); - private static final RegistryAccess DEFAULT_REGISTRY = CraftRegistry.getMinecraftRegistry(); + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - private static final CompoundTag NBT_EMPTY_ITEMSTACK = new CompoundTag(); + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); - static { - NBT_EMPTY_ITEMSTACK.putString("id", "minecraft:air"); - } + private static final DynamicOps DYNAMIC_OPS_NBT = DEFAULT_REGISTRY.createSerializationContext(NbtOps.INSTANCE); + private static final DynamicOps DYNAMIC_OPS_JSON = DEFAULT_REGISTRY.createSerializationContext(JsonOps.INSTANCE); - public byte[] nbtToBinary(CompoundTag compound) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - NbtIo.writeCompressed(compound, outputStream); - return outputStream.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); } - return null; + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; } - public CompoundTag binaryToNBT(byte[] binary) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { - return NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); - } catch (Exception e) { - e.printStackTrace(); + @Override + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); } - return new CompoundTag(); + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); + } + + return new ItemStackContainerResult(itemsSize, items); } - + @Override - public byte[] itemstackToBinary(ItemStack[] items) { - CompoundTag inventory = new CompoundTag(); - ListTag list = new ListTag(); - for (ItemStack itemStack : items) { - if (itemStack == null || itemStack.getType() == Material.AIR) { - list.add(NBT_EMPTY_ITEMSTACK); - } else { - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); - Tag tag = craftItem.save(DEFAULT_REGISTRY); - list.add(tag); - } + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); } - inventory.put("i", list); - return nbtToBinary(inventory); - } + + ListTag list = compound.getList("i", 10); - @Override - public List binaryToItemStack(byte[] binary) { - CompoundTag nbt = binaryToNBT(binary); - List items = new ArrayList<>(); - if (nbt.contains("i", 9)) { - ListTag list = nbt.getList("i", 10); - for (Tag base : list) { - if (base instanceof CompoundTag itemTag) { - if (itemTag.getString("id").equals("minecraft:air")) { - items.add(new ItemStack(Material.AIR)); - } else { - Optional optional = net.minecraft.world.item.ItemStack.parse(DEFAULT_REGISTRY, itemTag); - if (optional.isPresent()) { - items.add(CraftItemStack.asBukkitCopy(optional.get())); - } - } + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id"); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; } } - return items; + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; } @Override diff --git a/zip-nms/zip-nms-v1_21_R1/src/main/java/net/imprex/zip/nms/v1_21_R1/ZipNmsManager.java b/zip-nms/zip-nms-v1_21_R1/src/main/java/net/imprex/zip/nms/v1_21_R1/ZipNmsManager.java index 36248a6..dac5638 100644 --- a/zip-nms/zip-nms-v1_21_R1/src/main/java/net/imprex/zip/nms/v1_21_R1/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_21_R1/src/main/java/net/imprex/zip/nms/v1_21_R1/ZipNmsManager.java @@ -1,45 +1,60 @@ package net.imprex.zip.nms.v1_21_R1; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.function.BiConsumer; import org.bukkit.Material; -import org.bukkit.craftbukkit.v1_21_R1.CraftRegistry; import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtAccounter; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; import net.minecraft.world.item.component.ResolvableProfile; public class ZipNmsManager implements NmsManager { - private static final BiConsumer SET_PROFILE; - - private static final RegistryAccess DEFAULT_REGISTRY = CraftRegistry.getMinecraftRegistry(); + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - private static final CompoundTag NBT_EMPTY_ITEMSTACK = new CompoundTag(); + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + private static final DynamicOps DYNAMIC_OPS_NBT = DEFAULT_REGISTRY.createSerializationContext(NbtOps.INSTANCE); + private static final DynamicOps DYNAMIC_OPS_JSON = DEFAULT_REGISTRY.createSerializationContext(JsonOps.INSTANCE); + + private static final BiConsumer SET_PROFILE; + static { - NBT_EMPTY_ITEMSTACK.putString("id", "minecraft:air"); - BiConsumer setProfile = (meta, profile) -> { throw new NullPointerException("Unable to find 'setProfile' method!"); }; @@ -73,62 +88,105 @@ public class ZipNmsManager implements NmsManager { SET_PROFILE = setProfile; } - public byte[] nbtToBinary(CompoundTag compound) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - NbtIo.writeCompressed(compound, outputStream); - return outputStream.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - public CompoundTag binaryToNBT(byte[] binary) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { - return NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); - } catch (Exception e) { - e.printStackTrace(); + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); } - return new CompoundTag(); + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; } @Override - public byte[] itemstackToBinary(ItemStack[] items) { - CompoundTag inventory = new CompoundTag(); - ListTag list = new ListTag(); - for (ItemStack itemStack : items) { - if (itemStack == null || itemStack.getType() == Material.AIR) { - list.add(NBT_EMPTY_ITEMSTACK); - } else { - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); - Tag tag = craftItem.save(DEFAULT_REGISTRY); - list.add(tag); - } + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); } - inventory.put("i", list); - return nbtToBinary(inventory); + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); + } + + return new ItemStackContainerResult(itemsSize, items); } - + @Override - public List binaryToItemStack(byte[] binary) { - CompoundTag nbt = binaryToNBT(binary); - List items = new ArrayList<>(); - if (nbt.contains("i", 9)) { - ListTag list = nbt.getList("i", 10); - for (Tag base : list) { - if (base instanceof CompoundTag itemTag) { - if (itemTag.getString("id").equals("minecraft:air")) { - items.add(new ItemStack(Material.AIR)); - } else { - Optional optional = net.minecraft.world.item.ItemStack.parse(DEFAULT_REGISTRY, itemTag); - if (optional.isPresent()) { - items.add(CraftItemStack.asBukkitCopy(optional.get())); - } - } + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getList("i", 10); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id"); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; } } - return items; + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; } @Override diff --git a/zip-nms/zip-nms-v1_21_R2/src/main/java/net/imprex/zip/nms/v1_21_R2/ZipNmsManager.java b/zip-nms/zip-nms-v1_21_R2/src/main/java/net/imprex/zip/nms/v1_21_R2/ZipNmsManager.java index bd63f72..391ce46 100644 --- a/zip-nms/zip-nms-v1_21_R2/src/main/java/net/imprex/zip/nms/v1_21_R2/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_21_R2/src/main/java/net/imprex/zip/nms/v1_21_R2/ZipNmsManager.java @@ -1,45 +1,60 @@ package net.imprex.zip.nms.v1_21_R2; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.function.BiConsumer; import org.bukkit.Material; -import org.bukkit.craftbukkit.v1_21_R2.CraftRegistry; import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtAccounter; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; import net.minecraft.world.item.component.ResolvableProfile; public class ZipNmsManager implements NmsManager { - private static final BiConsumer SET_PROFILE; - - private static final RegistryAccess DEFAULT_REGISTRY = CraftRegistry.getMinecraftRegistry(); + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - private static final CompoundTag NBT_EMPTY_ITEMSTACK = new CompoundTag(); + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + private static final DynamicOps DYNAMIC_OPS_NBT = DEFAULT_REGISTRY.createSerializationContext(NbtOps.INSTANCE); + private static final DynamicOps DYNAMIC_OPS_JSON = DEFAULT_REGISTRY.createSerializationContext(JsonOps.INSTANCE); + + private static final BiConsumer SET_PROFILE; + static { - NBT_EMPTY_ITEMSTACK.putString("id", "minecraft:air"); - BiConsumer setProfile = (meta, profile) -> { throw new NullPointerException("Unable to find 'setProfile' method!"); }; @@ -73,62 +88,105 @@ public class ZipNmsManager implements NmsManager { SET_PROFILE = setProfile; } - public byte[] nbtToBinary(CompoundTag compound) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - NbtIo.writeCompressed(compound, outputStream); - return outputStream.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - public CompoundTag binaryToNBT(byte[] binary) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { - return NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); - } catch (Exception e) { - e.printStackTrace(); + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); } - return new CompoundTag(); + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; } @Override - public byte[] itemstackToBinary(ItemStack[] items) { - CompoundTag inventory = new CompoundTag(); - ListTag list = new ListTag(); - for (ItemStack itemStack : items) { - if (itemStack == null || itemStack.getType() == Material.AIR) { - list.add(NBT_EMPTY_ITEMSTACK); - } else { - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); - Tag tag = craftItem.save(DEFAULT_REGISTRY); - list.add(tag); - } + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); } - inventory.put("i", list); - return nbtToBinary(inventory); + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); + } + + return new ItemStackContainerResult(itemsSize, items); } - + @Override - public List binaryToItemStack(byte[] binary) { - CompoundTag nbt = binaryToNBT(binary); - List items = new ArrayList<>(); - if (nbt.contains("i", 9)) { - ListTag list = nbt.getList("i", 10); - for (Tag base : list) { - if (base instanceof CompoundTag itemTag) { - if (itemTag.getString("id").equals("minecraft:air")) { - items.add(new ItemStack(Material.AIR)); - } else { - Optional optional = net.minecraft.world.item.ItemStack.parse(DEFAULT_REGISTRY, itemTag); - if (optional.isPresent()) { - items.add(CraftItemStack.asBukkitCopy(optional.get())); - } - } + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getList("i", 10); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id"); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; } } - return items; + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; } @Override diff --git a/zip-nms/zip-nms-v1_21_R3/pom.xml b/zip-nms/zip-nms-v1_21_R3/pom.xml index 89e6386..61e819d 100644 --- a/zip-nms/zip-nms-v1_21_R3/pom.xml +++ b/zip-nms/zip-nms-v1_21_R3/pom.xml @@ -1,7 +1,5 @@ - - 4.0.0 + + 4.0.0 net.imprex diff --git a/zip-nms/zip-nms-v1_21_R3/src/main/java/net/imprex/zip/nms/v1_21_R3/ZipNmsManager.java b/zip-nms/zip-nms-v1_21_R3/src/main/java/net/imprex/zip/nms/v1_21_R3/ZipNmsManager.java index 7c2a710..bc8d73e 100644 --- a/zip-nms/zip-nms-v1_21_R3/src/main/java/net/imprex/zip/nms/v1_21_R3/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_21_R3/src/main/java/net/imprex/zip/nms/v1_21_R3/ZipNmsManager.java @@ -1,45 +1,60 @@ package net.imprex.zip.nms.v1_21_R3; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.function.BiConsumer; import org.bukkit.Material; -import org.bukkit.craftbukkit.v1_21_R3.CraftRegistry; import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtAccounter; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; import net.minecraft.world.item.component.ResolvableProfile; public class ZipNmsManager implements NmsManager { - private static final BiConsumer SET_PROFILE; - - private static final RegistryAccess DEFAULT_REGISTRY = CraftRegistry.getMinecraftRegistry(); + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - private static final CompoundTag NBT_EMPTY_ITEMSTACK = new CompoundTag(); + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + private static final DynamicOps DYNAMIC_OPS_NBT = DEFAULT_REGISTRY.createSerializationContext(NbtOps.INSTANCE); + private static final DynamicOps DYNAMIC_OPS_JSON = DEFAULT_REGISTRY.createSerializationContext(JsonOps.INSTANCE); + + private static final BiConsumer SET_PROFILE; + static { - NBT_EMPTY_ITEMSTACK.putString("id", "minecraft:air"); - BiConsumer setProfile = (meta, profile) -> { throw new NullPointerException("Unable to find 'setProfile' method!"); }; @@ -73,62 +88,105 @@ public class ZipNmsManager implements NmsManager { SET_PROFILE = setProfile; } - public byte[] nbtToBinary(CompoundTag compound) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - NbtIo.writeCompressed(compound, outputStream); - return outputStream.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - public CompoundTag binaryToNBT(byte[] binary) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { - return NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); - } catch (Exception e) { - e.printStackTrace(); + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); } - return new CompoundTag(); + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; } @Override - public byte[] itemstackToBinary(ItemStack[] items) { - CompoundTag inventory = new CompoundTag(); - ListTag list = new ListTag(); - for (ItemStack itemStack : items) { - if (itemStack == null || itemStack.getType() == Material.AIR) { - list.add(NBT_EMPTY_ITEMSTACK); - } else { - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); - Tag tag = craftItem.save(DEFAULT_REGISTRY); - list.add(tag); - } + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); } - inventory.put("i", list); - return nbtToBinary(inventory); + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); + } + + return new ItemStackContainerResult(itemsSize, items); } - + @Override - public List binaryToItemStack(byte[] binary) { - CompoundTag nbt = binaryToNBT(binary); - List items = new ArrayList<>(); - if (nbt.contains("i", 9)) { - ListTag list = nbt.getList("i", 10); - for (Tag base : list) { - if (base instanceof CompoundTag itemTag) { - if (itemTag.getString("id").equals("minecraft:air")) { - items.add(new ItemStack(Material.AIR)); - } else { - Optional optional = net.minecraft.world.item.ItemStack.parse(DEFAULT_REGISTRY, itemTag); - if (optional.isPresent()) { - items.add(CraftItemStack.asBukkitCopy(optional.get())); - } - } + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getList("i", 10); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id"); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; } } - return items; + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; } @Override diff --git a/zip-nms/zip-nms-v1_21_R4/src/main/java/net/imprex/zip/nms/v1_21_R4/ZipNmsManager.java b/zip-nms/zip-nms-v1_21_R4/src/main/java/net/imprex/zip/nms/v1_21_R4/ZipNmsManager.java index 4c51c9e..063907b 100644 --- a/zip-nms/zip-nms-v1_21_R4/src/main/java/net/imprex/zip/nms/v1_21_R4/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_21_R4/src/main/java/net/imprex/zip/nms/v1_21_R4/ZipNmsManager.java @@ -1,45 +1,60 @@ package net.imprex.zip.nms.v1_21_R4; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.function.BiConsumer; import org.bukkit.Material; -import org.bukkit.craftbukkit.v1_21_R4.CraftRegistry; import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; import net.minecraft.core.RegistryAccess; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtAccounter; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; import net.minecraft.world.item.component.ResolvableProfile; public class ZipNmsManager implements NmsManager { - private static final BiConsumer SET_PROFILE; - - private static final RegistryAccess DEFAULT_REGISTRY = CraftRegistry.getMinecraftRegistry(); + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); - private static final CompoundTag NBT_EMPTY_ITEMSTACK = new CompoundTag(); + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + private static final DynamicOps DYNAMIC_OPS_NBT = DEFAULT_REGISTRY.createSerializationContext(NbtOps.INSTANCE); + private static final DynamicOps DYNAMIC_OPS_JSON = DEFAULT_REGISTRY.createSerializationContext(JsonOps.INSTANCE); + + private static final BiConsumer SET_PROFILE; + static { - NBT_EMPTY_ITEMSTACK.putString("id", "minecraft:air"); - BiConsumer setProfile = (meta, profile) -> { throw new NullPointerException("Unable to find 'setProfile' method!"); }; @@ -73,67 +88,105 @@ public class ZipNmsManager implements NmsManager { SET_PROFILE = setProfile; } - public byte[] nbtToBinary(CompoundTag compound) { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - NbtIo.writeCompressed(compound, outputStream); - return outputStream.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - public CompoundTag binaryToNBT(byte[] binary) { - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { - return NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); - } catch (Exception e) { - e.printStackTrace(); - } - return new CompoundTag(); - } - @Override - public byte[] itemstackToBinary(ItemStack[] items) { - CompoundTag inventory = new CompoundTag(); - ListTag list = new ListTag(); - for (ItemStack itemStack : items) { - if (itemStack == null || itemStack.getType() == Material.AIR) { - list.add(NBT_EMPTY_ITEMSTACK); - } else { - net.minecraft.world.item.ItemStack craftItem = CraftItemStack.asNMSCopy(itemStack); - Tag tag = craftItem.save(DEFAULT_REGISTRY); - list.add(tag); + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); } - inventory.put("i", list); - return nbtToBinary(inventory); + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; } @Override - public List binaryToItemStack(byte[] binary) { - CompoundTag nbt = binaryToNBT(binary); - List items = new ArrayList<>(); - if (nbt.contains("i")) { - Optional list = nbt.getList("i"); - if (list.isEmpty()) { - return items; - } + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); + } + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); - for (Tag base : list.get()) { - if (base instanceof CompoundTag itemTag) { - String itemType = itemTag.getString("id").orElse(""); - if (itemType.equals("minecraft:air")) { - items.add(new ItemStack(Material.AIR)); - } else { - Optional optional = net.minecraft.world.item.ItemStack.parse(DEFAULT_REGISTRY, itemTag); - if (optional.isPresent()) { - items.add(CraftItemStack.asBukkitCopy(optional.get())); - } - } + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); + } + + return new ItemStackContainerResult(itemsSize, items); + } + + @Override + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getListOrEmpty("i"); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id").orElse(""); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; } } - return items; + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; } @Override diff --git a/zip-nms/zip-nms-v1_21_R5/pom.xml b/zip-nms/zip-nms-v1_21_R5/pom.xml new file mode 100644 index 0000000..2e6e44e --- /dev/null +++ b/zip-nms/zip-nms-v1_21_R5/pom.xml @@ -0,0 +1,70 @@ + + 4.0.0 + + + net.imprex + zip-nms + ${revision} + + + zip-nms-v1_21_R5 + + + + net.imprex + zip-nms-api + ${revision} + provided + + + org.spigotmc + spigot + 1.21.6-R0.1-SNAPSHOT + remapped-mojang + provided + + + + + + + net.md-5 + specialsource-maven-plugin + ${plugin.specialsource.version} + + + package + + remap + + remap-obf + + + org.spigotmc:minecraft-server:1.21.6-R0.1-SNAPSHOT:txt:maps-mojang + true + + org.spigotmc:spigot:1.21.6-R0.1-SNAPSHOT:jar:remapped-mojang + true + remapped-obf + + + + package + + remap + + remap-spigot + + + ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar + + org.spigotmc:minecraft-server:1.21.6-R0.1-SNAPSHOT:csrg:maps-spigot + + org.spigotmc:spigot:1.21.6-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + \ No newline at end of file diff --git a/zip-nms/zip-nms-v1_21_R5/src/main/java/net/imprex/zip/nms/v1_21_R5/ZipNmsManager.java b/zip-nms/zip-nms-v1_21_R5/src/main/java/net/imprex/zip/nms/v1_21_R5/ZipNmsManager.java new file mode 100644 index 0000000..41a9020 --- /dev/null +++ b/zip-nms/zip-nms-v1_21_R5/src/main/java/net/imprex/zip/nms/v1_21_R5/ZipNmsManager.java @@ -0,0 +1,208 @@ +package net.imprex.zip.nms.v1_21_R5; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.BiConsumer; + +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; + +import net.imprex.zip.common.BPConstants; +import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; +import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; +import net.minecraft.core.RegistryAccess; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; +import net.minecraft.world.item.component.ResolvableProfile; + +public class ZipNmsManager implements NmsManager { + + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().dataVersion().version(); + + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + + private static final DynamicOps DYNAMIC_OPS_NBT = DEFAULT_REGISTRY.createSerializationContext(NbtOps.INSTANCE); + private static final DynamicOps DYNAMIC_OPS_JSON = DEFAULT_REGISTRY.createSerializationContext(JsonOps.INSTANCE); + + private static final BiConsumer SET_PROFILE; + + static { + BiConsumer setProfile = (meta, profile) -> { + throw new NullPointerException("Unable to find 'setProfile' method!"); + }; + + Class craftMetaSkullClass = new ItemStack(Material.PLAYER_HEAD) + .getItemMeta() + .getClass(); + + Method setResolvableProfileMethod = ReflectionUtil.searchMethod(craftMetaSkullClass, void.class, ResolvableProfile.class); + if (setResolvableProfileMethod != null) { + setProfile = (meta, profile) -> { + try { + setResolvableProfileMethod.invoke(meta, new ResolvableProfile(profile)); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + }; + } else { + Method setProfileMethod = ReflectionUtil.searchMethod(craftMetaSkullClass, void.class, GameProfile.class); + if (setProfileMethod != null) { + setProfile = (meta, profile) -> { + try { + setProfileMethod.invoke(meta, profile); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + }; + } + } + + SET_PROFILE = setProfile; + } + + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); + } + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; + } + + @Override + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); + } + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); + } + + return new ItemStackContainerResult(itemsSize, items); + } + + @Override + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getListOrEmpty("i"); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id").orElse(""); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; + } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; + } + } + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; + } + + @Override + public void setSkullProfile(SkullMeta meta, String texture) { + try { + GameProfile gameProfile = new GameProfile(UUID.randomUUID(), ""); + gameProfile.getProperties().put("textures", new Property("textures", texture)); + + SET_PROFILE.accept(meta, gameProfile); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public boolean isAir(Material material) { + return material == null || material == Material.AIR; + } +} \ No newline at end of file diff --git a/zip-plugin/pom.xml b/zip-plugin/pom.xml index e52f7a3..7aa6882 100644 --- a/zip-plugin/pom.xml +++ b/zip-plugin/pom.xml @@ -113,5 +113,11 @@ ${revision} compile + + net.imprex + zip-nms-v1_21_R5 + ${revision} + compile + \ No newline at end of file diff --git a/zip-plugin/src/main/java/net/imprex/zip/Backpack.java b/zip-plugin/src/main/java/net/imprex/zip/Backpack.java index 4a2c20f..0ae87ce 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/Backpack.java +++ b/zip-plugin/src/main/java/net/imprex/zip/Backpack.java @@ -1,5 +1,9 @@ package net.imprex.zip; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -13,13 +17,21 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; +import com.google.gson.Gson; +import com.google.gson.JsonObject; + import net.imprex.zip.api.ZIPBackpack; -import net.imprex.zip.common.Ingrim4Buffer; +import net.imprex.zip.common.BPConstants; import net.imprex.zip.common.UniqueId; +import net.imprex.zip.common.ZIPLogger; import net.imprex.zip.config.MessageConfig; import net.imprex.zip.config.MessageKey; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; public class Backpack implements ZIPBackpack { + + static final Gson GSON = new Gson(); private final BackpackHandler backpackHandler; private final MessageConfig messageConfig; @@ -55,7 +67,7 @@ public Backpack(BackpackPlugin plugin, BackpackType type, UniqueId id) { this.save(); } - public Backpack(BackpackPlugin plugin, UniqueId id, Ingrim4Buffer buffer) { + public Backpack(BackpackPlugin plugin, UniqueId id, JsonObject json) { this.backpackHandler = plugin.getBackpackHandler(); this.messageConfig = plugin.getBackpackConfig().message(); this.identifierKey = plugin.getBackpackIdentifierKey(); @@ -65,14 +77,16 @@ public Backpack(BackpackPlugin plugin, UniqueId id, Ingrim4Buffer buffer) { * Load backpack id from buffer but don't use it! * Just for later migration to SQL */ - buffer.readByteArray(); + + this.id = id; - this.typeRaw = buffer.readString(); + this.typeRaw = json.get(BPConstants.KEY_TYPE_RAW).getAsString(); this.type = plugin.getBackpackRegistry().getTypeByName(this.typeRaw); - byte[] contentAsByteArray = buffer.readByteArray(); - ItemStack[] content = NmsInstance.binaryToItemStack(contentAsByteArray).toArray(ItemStack[]::new); + JsonObject contentAsJson = json.getAsJsonObject(BPConstants.KEY_INVENTORY); + ItemStackContainerResult contentResult = NmsInstance.jsonElementToItemStack(contentAsJson); + ItemStack[] content = this.parseItemStackList(contentResult); this.content = content; if (this.type != null) { @@ -97,8 +111,70 @@ public Backpack(BackpackPlugin plugin, UniqueId id, Ingrim4Buffer buffer) { this.backpackHandler.registerBackpack(this); } + + private ItemStack[] parseItemStackList(ItemStackContainerResult result) { + int containerSize = result.containerSize(); + + ItemStack[] items = new ItemStack[containerSize]; + Arrays.fill(items, new ItemStack(Material.AIR)); + + List duplicateSlot = null; + + for (ItemStackWithSlot itemWithSlot : result.items()) { + ItemStack item = itemWithSlot.item(); + int slot = itemWithSlot.slot(); + + if (containerSize <= slot) { + // something went wrong !? maybe user modified it him self + ZIPLogger.warn("Slot size was extended from " + containerSize + " to " + slot + " this should not happen. Do not change the slot number inside the config manually!?"); + + ItemStack[] newItems = new ItemStack[slot + 1]; + System.arraycopy(items, 0, newItems, 0, items.length); + Arrays.fill(newItems, items.length, newItems.length, new ItemStack(Material.AIR)); + items = newItems; + } + + if (items[slot].getType() != Material.AIR) { + if (duplicateSlot == null) { + duplicateSlot = new ArrayList<>(); + } + duplicateSlot.add(item); + ZIPLogger.warn("Duplicate item found on slot " + slot + " this should not happen. Do not change the slot number inside the config manually!?"); + } else { + items[slot] = item; + } + } + + // fill existing empty slots with duplicate item + while (duplicateSlot != null && !duplicateSlot.isEmpty()) { + outher: for (Iterator iterator = duplicateSlot.iterator(); iterator.hasNext();) { + ItemStack itemStack = (ItemStack) iterator.next(); + + for (int i = 0; i < items.length; i++) { + if (items[i].getType() == Material.AIR) { + items[i] = itemStack; + iterator.remove(); + break; + } else if (i == items.length - 1) { + break outher; + } + } + } - public void save(Ingrim4Buffer buffer) { + // extend slot limit and try again + if (!duplicateSlot.isEmpty()) { + int extendedSlots = items.length + duplicateSlot.size(); + ItemStack[] newItems = new ItemStack[extendedSlots]; + System.arraycopy(items, 0, newItems, 0, items.length); + Arrays.fill(newItems, items.length, newItems.length, new ItemStack(Material.AIR)); + items = newItems; + } + } + + return items; + } + + public void save(JsonObject json) { if (this.inventory != null) { for (int i = 0; i < this.inventory.getSize(); i++) { this.content[i] = this.inventory.getItem(i); @@ -107,9 +183,10 @@ public void save(Ingrim4Buffer buffer) { throw new NullPointerException("content can not be null"); } - buffer.writeByteArray(this.id.toByteArray()); - buffer.writeString(this.typeRaw); - buffer.writeByteArray(NmsInstance.itemstackToBinary(this.content)); + json.addProperty(BPConstants.KEY_VERSION, BPConstants.VERSION); + json.addProperty(BPConstants.KEY_ID, this.id.toString()); + json.addProperty(BPConstants.KEY_TYPE_RAW, this.typeRaw); + json.add(BPConstants.KEY_INVENTORY, NmsInstance.itemstackToJsonElement(this.content)); } @Override @@ -203,6 +280,8 @@ public boolean giveUnsueableContent(Player player) { this.content[i] = null; } } + + this.save(); return empty; } diff --git a/zip-plugin/src/main/java/net/imprex/zip/BackpackHandler.java b/zip-plugin/src/main/java/net/imprex/zip/BackpackHandler.java index 5dd7c2d..a39970c 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/BackpackHandler.java +++ b/zip-plugin/src/main/java/net/imprex/zip/BackpackHandler.java @@ -3,6 +3,9 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -17,16 +20,14 @@ import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; -import com.google.common.io.ByteStreams; +import com.google.gson.JsonObject; -import io.netty.buffer.Unpooled; import net.imprex.zip.api.ZIPBackpack; import net.imprex.zip.api.ZIPBackpackType; import net.imprex.zip.api.ZIPHandler; import net.imprex.zip.api.ZIPUniqueId; -import net.imprex.zip.common.Ingrim4Buffer; import net.imprex.zip.common.UniqueId; -import net.imprex.zip.util.ZIPLogger; +import net.imprex.zip.common.ZIPLogger; public class BackpackHandler implements ZIPHandler { @@ -65,17 +66,25 @@ public void disable() { } private Backpack loadBackpack(UniqueId id) { - Path file = this.folderPath.resolve(id.toString()); + Path file = this.getPathForId(id); if (!Files.isRegularFile(file)) { - return null; + // migrate backpack to json + if (!BackpackMigrator.migrate(this.folderPath, id)) { + return null; + } + + // backpack was successful migrated } - try (FileInputStream inputStream = new FileInputStream(file.toFile())) { - byte[] data = ByteStreams.toByteArray(inputStream); - Ingrim4Buffer buffer = new Ingrim4Buffer(Unpooled.wrappedBuffer(data)); - - Backpack backpack = new Backpack(this.plugin, id, buffer); + try { + JsonObject json; + try (FileInputStream inputStream = new FileInputStream(file.toFile()); + InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + json = Backpack.GSON.fromJson(inputStreamReader, JsonObject.class); + } + + Backpack backpack = new Backpack(this.plugin, id, json); return backpack; } catch (Exception e) { ZIPLogger.error("Unable to load backpack for id '" + file.getFileName().toString() + "'", e); @@ -92,15 +101,18 @@ public void save(ZIPBackpack backpack) { e.printStackTrace(); } } + + JsonObject json = new JsonObject(); + if (backpack instanceof Backpack internalBackpack) { + internalBackpack.save(json); + } else { + throw new IllegalArgumentException("Backpack object is not a ZIPBackpack"); + } - Path file = this.folderPath.resolve(backpack.getId().toString()); - try (FileOutputStream outputStream = new FileOutputStream(file.toFile())) { - Ingrim4Buffer buffer = new Ingrim4Buffer(Unpooled.buffer()); - ((Backpack) backpack).save(buffer); - - byte[] bytes = new byte[buffer.readableBytes()]; - buffer.readBytes(bytes); - outputStream.write(bytes); + Path file = this.getPathForId(backpack.getId()); + try (FileOutputStream outputStream = new FileOutputStream(file.toFile()); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { + Backpack.GSON.toJson(json, outputStreamWriter); } catch (IOException e) { e.printStackTrace(); } @@ -154,6 +166,10 @@ public Backpack getBackpack(ItemStack item) { } } + if (this.loadingIssue.contains(uniqueId)) { + return null; + } + if (dataContainer.has(this.backpackIdentifierKey, PersistentDataType.STRING)) { String backpackIdentifier = dataContainer.get(this.backpackIdentifierKey, PersistentDataType.STRING); BackpackType backpackType = this.registry.getTypeByName(backpackIdentifier); @@ -201,4 +217,27 @@ public boolean isBackpack(ItemStack item) { return false; } + + @Override + public UniqueId getUniqueId(ItemStack item) { + if (item != null && item.hasItemMeta()) { + ItemMeta meta = item.getItemMeta(); + PersistentDataContainer dataContainer = meta.getPersistentDataContainer(); + + if (dataContainer.has(this.backpackStorageKey, PersistentDataType.BYTE_ARRAY)) { + byte[] storageKey = dataContainer.get(this.backpackStorageKey, PersistentDataType.BYTE_ARRAY); + return UniqueId.fromByteArray(storageKey); + } + } + + return null; + } + + private Path getPathForId(ZIPUniqueId id) { + return this.folderPath.resolve(id.toString() + ".json"); + } + + public Path getFolderPath() { + return this.folderPath; + } } \ No newline at end of file diff --git a/zip-plugin/src/main/java/net/imprex/zip/BackpackListener.java b/zip-plugin/src/main/java/net/imprex/zip/BackpackListener.java index da489c0..7bc4d03 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/BackpackListener.java +++ b/zip-plugin/src/main/java/net/imprex/zip/BackpackListener.java @@ -19,6 +19,7 @@ import org.bukkit.inventory.ItemStack; import net.imprex.zip.api.ZIPBackpackType; +import net.imprex.zip.common.UniqueId; import net.imprex.zip.config.MessageConfig; import net.imprex.zip.config.MessageKey; @@ -114,7 +115,9 @@ public void onPlayerInteract(PlayerInteractEvent event) { if (backpack != null) { backpack.open(event.getPlayer()); } else { - this.messageConfig.send(event.getPlayer(), MessageKey.UnableToLoadBackpack); + UniqueId id = this.backpackHandler.getUniqueId(event.getItem()); + String idString = id != null ? id.toString() : "NOT READABLE"; + this.messageConfig.send(event.getPlayer(), MessageKey.UnableToLoadBackpack, idString); } } } diff --git a/zip-plugin/src/main/java/net/imprex/zip/BackpackMigrator.java b/zip-plugin/src/main/java/net/imprex/zip/BackpackMigrator.java new file mode 100644 index 0000000..774906d --- /dev/null +++ b/zip-plugin/src/main/java/net/imprex/zip/BackpackMigrator.java @@ -0,0 +1,125 @@ +package net.imprex.zip; + +import java.awt.geom.IllegalPathStateException; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +import com.google.common.io.ByteStreams; +import com.google.gson.JsonObject; + +import io.netty.buffer.Unpooled; +import net.imprex.zip.common.BPConstants; +import net.imprex.zip.common.Ingrim4Buffer; +import net.imprex.zip.common.UniqueId; +import net.imprex.zip.common.ZIPLogger; + +public class BackpackMigrator { + + /* + * Previous implementation + * + * LOAD + * read id buffer.readByteArray(); + * read typeRaw buffer.readString(); + * read content buffer.readByteArray(); + * + * SAVE + * buffer.writeByteArray(this.id.toByteArray()); + * buffer.writeString(this.typeRaw); + * buffer.writeByteArray(NmsInstance.itemstackToBinary(this.content)); + */ + + public static void checkForMigrations(Path folderPath) { + long startTime = System.currentTimeMillis(); + int statisticSuccessful = 0; + int statisticFailed = 0; + + ZIPLogger.info("Checking for migration data..."); + + try (Stream stream = Files.walk(folderPath, 1)) { + Path[] paths = stream + .filter(file -> !Files.isDirectory(file)) + .filter(file -> Files.isRegularFile(file)) + .filter(file -> !file.getFileName().toString().endsWith(".json")) + .toArray(Path[]::new); + + ZIPLogger.info("Migration found " + paths.length + " outdated backpacks."); + + if (paths.length == 0) { + return; + } + + for (Path file : paths) { + try { + UniqueId id = UniqueId.fromString(file.getFileName().toString()); + if (BackpackMigrator.migrate(folderPath, id)) { + statisticSuccessful++; + } else { + statisticFailed++; + } + } catch (Exception e) { + e.printStackTrace(); + statisticFailed++; + } + } + + ZIPLogger.info(String.format("Migration finished. %d/%d backpacks migrated and %d failed to migrate in %d seconds.", + paths.length, + statisticSuccessful, + statisticFailed, + Math.round((System.currentTimeMillis() - startTime) / 1000))); + } catch (IOException e) { + ZIPLogger.error("Error when migrating backpacks", e); + } + } + + public static boolean migrate(Path folderPath, UniqueId id) { + try { + Path previousFile = folderPath.resolve(id.toString()); + + if (!Files.isRegularFile(previousFile)) { + return false; + } + + Ingrim4Buffer buffer; + try (FileInputStream inputStream = new FileInputStream(previousFile.toFile())) { + byte[] data = ByteStreams.toByteArray(inputStream); + buffer = new Ingrim4Buffer(Unpooled.wrappedBuffer(data)); + } + + byte[] previousId = buffer.readByteArray(); + String previousRawType = buffer.readString(); + byte[] previousContent = buffer.readByteArray(); + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_VERSION, BPConstants.VERSION); + json.addProperty(BPConstants.KEY_ID, UniqueId.fromByteArray(previousId).toString()); + json.addProperty(BPConstants.KEY_TYPE_RAW, previousRawType); + json.add(BPConstants.KEY_INVENTORY, NmsInstance.migrateToJsonElement(previousContent)); + + Path newFile = folderPath.resolve(id.toString() + ".json"); + if (Files.exists(newFile)) { + throw new IllegalPathStateException("File path for migration " + id.toString() + " already exist!"); + } + + try (FileOutputStream outputStream = new FileOutputStream(newFile.toFile()); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)) { + Backpack.GSON.toJson(json, outputStreamWriter); + } + + Files.deleteIfExists(previousFile); + + ZIPLogger.info("Successful migrated backpack id '" + id.toString() + "'"); + return true; + } catch (Exception e) { + ZIPLogger.error("Unable to migrate backpack id '" + id.toString() + "'", e); + } + return false; + } +} diff --git a/zip-plugin/src/main/java/net/imprex/zip/BackpackPlugin.java b/zip-plugin/src/main/java/net/imprex/zip/BackpackPlugin.java index 2d4d5a2..2f9fdb5 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/BackpackPlugin.java +++ b/zip-plugin/src/main/java/net/imprex/zip/BackpackPlugin.java @@ -17,8 +17,8 @@ import net.imprex.zip.api.ZIPService; import net.imprex.zip.command.BackpackCommand; +import net.imprex.zip.common.ZIPLogger; import net.imprex.zip.config.BackpackConfig; -import net.imprex.zip.util.ZIPLogger; public class BackpackPlugin extends JavaPlugin implements Listener, ZIPService { @@ -45,6 +45,8 @@ public void onLoad() { public void onEnable() { try { NmsInstance.initialize(); + + BackpackMigrator.checkForMigrations(this.backpackHandler.getFolderPath()); this.backpackConfig.deserialize(); diff --git a/zip-plugin/src/main/java/net/imprex/zip/BackpackRegistry.java b/zip-plugin/src/main/java/net/imprex/zip/BackpackRegistry.java index fb90f96..9a0f8c0 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/BackpackRegistry.java +++ b/zip-plugin/src/main/java/net/imprex/zip/BackpackRegistry.java @@ -12,10 +12,10 @@ import net.imprex.zip.api.ZIPBackpackType; import net.imprex.zip.api.ZIPRecipe; import net.imprex.zip.api.ZIPRegistry; +import net.imprex.zip.common.ZIPLogger; import net.imprex.zip.config.BackpackConfig; import net.imprex.zip.config.BackpackTypeConfig; import net.imprex.zip.config.BackpackTypeListConfig; -import net.imprex.zip.util.ZIPLogger; public class BackpackRegistry implements ZIPRegistry { diff --git a/zip-plugin/src/main/java/net/imprex/zip/NmsInstance.java b/zip-plugin/src/main/java/net/imprex/zip/NmsInstance.java index d4afe3e..1232970 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/NmsInstance.java +++ b/zip-plugin/src/main/java/net/imprex/zip/NmsInstance.java @@ -2,15 +2,17 @@ package net.imprex.zip; import java.lang.reflect.Constructor; -import java.util.List; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import com.google.gson.JsonObject; + import net.imprex.zip.common.MinecraftVersion; +import net.imprex.zip.common.ZIPLogger; +import net.imprex.zip.nms.api.ItemStackContainerResult; import net.imprex.zip.nms.api.NmsManager; -import net.imprex.zip.util.ZIPLogger; public class NmsInstance { @@ -38,12 +40,16 @@ public static void initialize() { ZIPLogger.info("NMS adapter for server version \"" + nmsVersion + "\" found!"); } - public static byte[] itemstackToBinary(ItemStack[] items) { - return instance.itemstackToBinary(items); + public static JsonObject itemstackToJsonElement(ItemStack[] items) { + return instance.itemstackToJsonElement(items); + } + + public static ItemStackContainerResult jsonElementToItemStack(JsonObject jsonElement) { + return instance.jsonElementToItemStack(jsonElement); } - public static List binaryToItemStack(byte[] binary) { - return instance.binaryToItemStack(binary); + public static JsonObject migrateToJsonElement(byte[] binary) { + return instance.migrateToJsonElement(binary); } public static void setSkullProfile(SkullMeta meta, String texture) { diff --git a/zip-plugin/src/main/java/net/imprex/zip/UpdateSystem.java b/zip-plugin/src/main/java/net/imprex/zip/UpdateSystem.java index b650306..a8cc6f4 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/UpdateSystem.java +++ b/zip-plugin/src/main/java/net/imprex/zip/UpdateSystem.java @@ -1,59 +1,66 @@ +/** + * @author Imprex-Development + * @see UpdateSystem.java + */ package net.imprex.zip; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; + +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; +import com.google.gson.annotations.SerializedName; +import net.imprex.zip.common.MinecraftVersion; +import net.imprex.zip.common.Version; +import net.imprex.zip.common.ZIPLogger; import net.imprex.zip.config.GeneralConfig; import net.imprex.zip.config.MessageConfig; import net.imprex.zip.config.MessageKey; -import net.imprex.zip.util.ZIPLogger; +import net.imprex.zip.util.AbstractHttpService; +import net.imprex.zip.util.ConsoleUtil; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.HoverEvent; import net.md_5.bungee.api.chat.hover.content.Text; -/** - * @author Imprex-Development - * @see UpdateSystem.java - */ -public class UpdateSystem { - - private static final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)(?:-b(\\d+))?"); +public class UpdateSystem extends AbstractHttpService { - private static final String API_LATEST = "https://api.github.com/repos/Imprex-Development/zero-inventory-problems/releases/latest"; - private static final long UPDATE_COOLDOWN = 1_800_000L; // 30min + private static final Pattern DEV_VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)(?:-b(?\\d+))?"); - private static final String repeatString(String message, int repeat) { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < repeat; i++) { - stringBuilder.append(message); - } - return stringBuilder.toString(); + private static boolean isDevVersion(String version) { + Matcher matcher = DEV_VERSION_PATTERN.matcher(version); + return matcher.find() && matcher.group("build") != null; } - private final Lock lock = new ReentrantLock(); + private static final String API_URI = "https://api.modrinth.com/v2/project/zero-inventory-problems-zip-backpacks/version?loaders=%s&game_versions=%s"; + private static final String DOWNLOAD_URI = "https://modrinth.com/plugin/zero-inventory-problems-zip-backpacks/version/%s"; + + private static final Duration CACHE_DURATION = Duration.ofMinutes(10L); private final BackpackPlugin plugin; private final GeneralConfig generalConfig; private final MessageConfig messageConfig; - private JsonObject releaseData; - private long updateCooldown = -1; - private int failedAttempts = 0; + private final AtomicReference validUntil = new AtomicReference<>(); + private final AtomicReference>> latestVersion = new AtomicReference<>(); public UpdateSystem(BackpackPlugin plugin) { + super(plugin); + this.plugin = plugin; this.generalConfig = plugin.getBackpackConfig().general(); this.messageConfig = plugin.getBackpackConfig().message(); @@ -61,98 +68,105 @@ public UpdateSystem(BackpackPlugin plugin) { this.checkForUpdates(); } - private JsonObject getReleaseData() { - this.lock.lock(); - try { - long systemTime = System.currentTimeMillis(); - - if (this.failedAttempts < 5) { - - if (this.releaseData != null || systemTime - this.updateCooldown > UPDATE_COOLDOWN) { - try { - URL url = new URL(API_LATEST); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - try (InputStreamReader inputStreamReader = new InputStreamReader(connection.getInputStream())) { - this.releaseData = JsonParser.parseReader(inputStreamReader).getAsJsonObject(); - this.updateCooldown = systemTime; - } - } catch (IOException e) { - ZIPLogger.warn("Unable to fetch latest update from: " + API_LATEST); - ZIPLogger.warn(e.toString()); - - if (++this.failedAttempts == 5) { - this.updateCooldown = systemTime; - } - } - } - - } else if (systemTime - this.updateCooldown > UPDATE_COOLDOWN) { - this.failedAttempts = 0; - this.updateCooldown = -1; - return this.getReleaseData(); - } - - return this.releaseData; - } finally { - this.lock.unlock(); + private CompletableFuture> requestLatestVersion() { + String installedVersion = this.plugin.getDescription().getVersion(); + if (!this.generalConfig.checkForUpdates || isDevVersion(installedVersion)) { + ZIPLogger.debug("UpdateSystem - Update check disabled or dev version detected; skipping"); + return CompletableFuture.completedFuture(Optional.empty()); } - } - private String getTagName() { - JsonObject releaseData = this.getReleaseData(); - if (releaseData != null && releaseData.has("tag_name")) { - return releaseData.getAsJsonPrimitive("tag_name").getAsString(); - } - return null; + var uri = String.format(API_URI, "bukkit", MinecraftVersion.current()); + return HTTP.sendAsync(request(uri).build(), json(ModrinthVersion[].class)).thenApply(request -> { + var version = Version.parse(installedVersion); + var latestVersion = Arrays.stream(request.body()) + .filter(e -> Objects.equals(e.versionType, "release")) + .filter(e -> Objects.equals(e.status, "listed")) + .sorted(Comparator.reverseOrder()) + .findFirst(); + + latestVersion.ifPresentOrElse( + v -> ZIPLogger.debug("UpdateSystem - Fetched latest version " + v.version), + () -> ZIPLogger.debug("UpdateSystem - Couldn't fetch latest version")); + + return latestVersion.map(v -> version.isBelow(v.version) ? v : null); + }).exceptionally(throwable -> { + ZIPLogger.warn("UpdateSystem - Unable to fetch latest version", throwable); + return Optional.empty(); + }); } - private String getHtmlUrl() { - JsonObject releaseData = this.getReleaseData(); - if (releaseData != null && releaseData.has("html_url")) { - return releaseData.getAsJsonPrimitive("html_url").getAsString(); + private CompletableFuture> getLatestVersion() { + Instant validUntil = this.validUntil.get(); + if (validUntil != null && validUntil.compareTo(Instant.now()) < 0 && this.validUntil.compareAndSet(validUntil, null)) { + ZIPLogger.debug("UpdateSystem - Cleared latest cached version"); + this.latestVersion.set(null); } - return null; - } - private boolean isDevVersion(String version) { - Matcher matcher = VERSION_PATTERN.matcher(version); - return matcher.find() && matcher.groupCount() == 4; + CompletableFuture> existingFuture = this.latestVersion.get(); + if (existingFuture != null) { + return existingFuture; + } + + CompletableFuture> newFuture = new CompletableFuture<>(); + if (this.latestVersion.compareAndSet(null, newFuture)) { + ZIPLogger.debug("UpdateSystem - Starting to check for updates"); + this.requestLatestVersion().thenAccept(version -> { + this.validUntil.set(Instant.now().plus(CACHE_DURATION)); + newFuture.complete(version); + }); + return newFuture; + } else { + return this.latestVersion.get(); + } } - private boolean isUpdateAvailable() { - String version = this.plugin.getDescription().getVersion(); - if (this.generalConfig.checkForUpdates && !this.isDevVersion(version)) { - String tagName = this.getTagName(); - return tagName != null && !version.equals(tagName); - } - return false; + private void ifNewerVersionAvailable(Consumer consumer) { + this.getLatestVersion().thenAccept(o -> o.ifPresent(consumer)); } private void checkForUpdates() { - Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { - if (this.isUpdateAvailable()) { - String url = " " + this.getHtmlUrl() + " "; - int lineLength = (int) Math.ceil((url.length() - 18) / 2d); - String line = repeatString("=", lineLength); - - ZIPLogger.warn(line + " Update available " + line); - ZIPLogger.warn(url); - ZIPLogger.warn(repeatString("=", lineLength * 2 + 18)); - } + this.ifNewerVersionAvailable(version -> { + String downloadUri = String.format(DOWNLOAD_URI, version.version); + ConsoleUtil.printBox(Level.WARNING, "UPDATE AVAILABLE", "", downloadUri); }); } public void checkForUpdates(Player player) { - Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { - if (this.isUpdateAvailable()) { - BaseComponent[] components = new ComponentBuilder(String.format("%s%s ", this.messageConfig.get(MessageKey.ANewReleaseIsAvailable))) - .append(this.messageConfig.getWithoutPrefix(MessageKey.ClickHere)) - .event(new ClickEvent(ClickEvent.Action.OPEN_URL, this.getHtmlUrl())) - .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(this.messageConfig.getWithoutPrefix(MessageKey.ClickHereToSeeTheLatestRelease)))).create(); - Bukkit.getScheduler().runTask(this.plugin, () -> { - player.spigot().sendMessage(components); - }); - } + this.ifNewerVersionAvailable(version -> { + String downloadUri = String.format(DOWNLOAD_URI, version.version); + BaseComponent[] components = new ComponentBuilder(String.format("%s%s ", this.messageConfig.get(MessageKey.ANewReleaseIsAvailable))) + .append(this.messageConfig.getWithoutPrefix(MessageKey.ClickHere)) + .event(new ClickEvent(ClickEvent.Action.OPEN_URL, downloadUri)) + .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(new ComponentBuilder(this.messageConfig.getWithoutPrefix(MessageKey.ClickHereToSeeTheLatestRelease)).create()))).create(); + Bukkit.getScheduler().runTask(this.plugin, () -> { + player.spigot().sendMessage(components); + }); }); } -} \ No newline at end of file + + public static class ModrinthVersion implements Comparable { + + private static final Comparator COMPARATOR = + Comparator.comparing(e -> e.version, Comparator.nullsLast(Version::compareTo)); + + @SerializedName("version_number") + public Version version; + + @SerializedName("game_versions") + public List gameVersions; + + @SerializedName("version_type") + public String versionType; + + @SerializedName("loaders") + public List loaders; + + @SerializedName("status") + public String status; + + @Override + public int compareTo(ModrinthVersion other) { + return COMPARATOR.compare(this, other); + } + } +} diff --git a/zip-plugin/src/main/java/net/imprex/zip/command/LinkCommand.java b/zip-plugin/src/main/java/net/imprex/zip/command/LinkCommand.java index 49dbe18..da4f1e3 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/command/LinkCommand.java +++ b/zip-plugin/src/main/java/net/imprex/zip/command/LinkCommand.java @@ -71,6 +71,9 @@ public void onCommand(CommandSender sender, String[] args) { } else if (linkingBackpack.equals(backpack)) { this.messageConfig.send(player, MessageKey.ThisBackpackIsAlreadyLinkedThoThat); return; + } else if (item.getAmount() > 1) { + this.messageConfig.send(player, MessageKey.StackedBackpacksCanNotBeLinked); + return; } linkingBackpack.applyOnItem(item); diff --git a/zip-plugin/src/main/java/net/imprex/zip/config/MessageConfig.java b/zip-plugin/src/main/java/net/imprex/zip/config/MessageConfig.java index aa40f5a..bf81c99 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/config/MessageConfig.java +++ b/zip-plugin/src/main/java/net/imprex/zip/config/MessageConfig.java @@ -20,7 +20,7 @@ import org.bukkit.configuration.file.YamlConfiguration; import net.imprex.zip.BackpackPlugin; -import net.imprex.zip.util.ZIPLogger; +import net.imprex.zip.common.ZIPLogger; import net.md_5.bungee.api.ChatColor; public class MessageConfig { diff --git a/zip-plugin/src/main/java/net/imprex/zip/config/MessageKey.java b/zip-plugin/src/main/java/net/imprex/zip/config/MessageKey.java index 50e99e5..bf4bfeb 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/config/MessageKey.java +++ b/zip-plugin/src/main/java/net/imprex/zip/config/MessageKey.java @@ -54,13 +54,14 @@ public enum MessageKey { YourBackpackLinkRequestWasCancelled("yourBackpackLinkRequestWasCancelled", "Your backpack link request was cancelled"), BothBackpacksNeedToBeTheSameType("bothBackpacksNeedToBeTheSameType", "Both Backpacks need to be the same type"), ThisBackpackIsAlreadyLinkedThoThat("thisBackpackIsAlreadyLinkedThoThat", "This backpack is already linked to that backpack"), + StackedBackpacksCanNotBeLinked("stackedBackpacksCanNotBeLinked", "Stacked backpacks can not be linked at the same time"), PleaseEnterANumber("pleaseEnterANumber", "Please enter a number"), EnterANumberBetweenArgsAndArgs("enterANumberBetweenArgsAndArgs", "Please enter a number between {0} and {1}"), LoreLineCreate("loreLineCreate", "The lore line {0} was added"), LoreLineChange("loreLineChange", "The lore line {0} was changed"), LoreLineDelete("loreLineDelete", "The lore line {0} was deleted"), MaxLoreCountReached("maxLoreCountReached", "You have reached the max lore count of {0}"), - UnableToLoadBackpack("unableToLoadBackpack", "Backpack can't be loaded!"); + UnableToLoadBackpack("unableToLoadBackpack", "Backpack id §8\"§e{0}§8\" §7can't be loaded!"); public static MessageKey findByKey(String key) { for (MessageKey messageKey : values()) { diff --git a/zip-plugin/src/main/java/net/imprex/zip/config/RecipeConfig.java b/zip-plugin/src/main/java/net/imprex/zip/config/RecipeConfig.java index 99d4d3c..67c335b 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/config/RecipeConfig.java +++ b/zip-plugin/src/main/java/net/imprex/zip/config/RecipeConfig.java @@ -6,7 +6,7 @@ import org.bukkit.Material; import org.bukkit.configuration.ConfigurationSection; -import net.imprex.zip.util.ZIPLogger; +import net.imprex.zip.common.ZIPLogger; public class RecipeConfig { diff --git a/zip-plugin/src/main/java/net/imprex/zip/util/AbstractHttpService.java b/zip-plugin/src/main/java/net/imprex/zip/util/AbstractHttpService.java new file mode 100644 index 0000000..2c0fee8 --- /dev/null +++ b/zip-plugin/src/main/java/net/imprex/zip/util/AbstractHttpService.java @@ -0,0 +1,56 @@ +/** + * @author Imprex-Development + * @see AbstractHttpService.java + */ +package net.imprex.zip.util; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse.BodyHandler; +import java.net.http.HttpResponse.BodySubscribers; + +import org.bukkit.plugin.PluginDescriptionFile; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import net.imprex.zip.BackpackPlugin; +import net.imprex.zip.common.Version; + +public abstract class AbstractHttpService { + + public static final Gson GSON = new GsonBuilder().setPrettyPrinting() + .registerTypeAdapter(Version.class, new Version.Json()) + .create(); + + public static final HttpClient HTTP = HttpClient.newHttpClient(); + + protected final String userAgent; + + public AbstractHttpService(BackpackPlugin plugin) { + PluginDescriptionFile pluginDescription = plugin.getDescription(); + this.userAgent = String.format("%s/%s", pluginDescription.getName(), pluginDescription.getVersion()); + } + + protected HttpRequest.Builder request(String url) { + return HttpRequest.newBuilder(URI.create(url)) + .header("User-Agent", userAgent) + .header("Accept", "application/json"); + } + + protected static BodyHandler json(Class target) { + return (responseInfo) -> responseInfo.statusCode() == 200 + ? BodySubscribers.mapping(BodySubscribers.ofInputStream(), inputStream -> { + try (InputStreamReader reader = new InputStreamReader(inputStream)) { + return GSON.fromJson(new InputStreamReader(inputStream), target); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + }) + : BodySubscribers.replacing(null); + } +} diff --git a/zip-plugin/src/main/java/net/imprex/zip/util/ConsoleUtil.java b/zip-plugin/src/main/java/net/imprex/zip/util/ConsoleUtil.java new file mode 100644 index 0000000..733aa52 --- /dev/null +++ b/zip-plugin/src/main/java/net/imprex/zip/util/ConsoleUtil.java @@ -0,0 +1,115 @@ +/** + * @author Imprex-Development + * @see ConsoleUtil.java + */ +package net.imprex.zip.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; + +import org.bukkit.ChatColor; + +import net.imprex.zip.common.ZIPLogger; + +public final class ConsoleUtil { + + private static final int BOX_PADDING = 3; + private static final int BOX_PREFERRED_WIDTH = 48; + + private ConsoleUtil() { + } + + public static String replaceAnsiColorWithChatColor(String value) { + value = value.replaceAll("\u001B\\[m", ChatColor.RESET.toString()); + value = value.replaceAll("\u001B\\[31;1m", ChatColor.RED.toString()); + value = value.replaceAll("\u001B\\[33;1m", ChatColor.YELLOW.toString()); + return value; + } + + public static void printBox(Level level, String...lines) { + for (String line : createBox(lines)) { + ZIPLogger.log(level, line); + } + } + + /** + * Creates a ASCII box around the given lines + */ + public static Iterable createBox(String...lines) { + + List wrappedLines = new ArrayList<>(); + for (String line : lines) { + line = line.trim(); + + while (line.length() > BOX_PREFERRED_WIDTH) { + + int splitLength = 0; + for (int i = 0; i < line.length(); i++) { + if (Character.isWhitespace(line.charAt(i))) { + if (i <= BOX_PREFERRED_WIDTH) { + splitLength = i; + } else { + break; + } + } + } + + // can't split line no whitespace character found + if (splitLength == 0) { + break; + } + + // split line at latest word that fit length + wrappedLines.add(line.substring(0, splitLength)); + line = line.substring(splitLength, line.length()).trim(); + } + + // add remainder + wrappedLines.add(line); + } + + // get max line width + int width = 0; + for (String line : wrappedLines) { + width = Math.max(width, line.length()); + } + + // add padding + int totalWidth = width + BOX_PADDING * 2; + + // create top/bottom lines + String bottomTopLine = repeat('-', totalWidth); + String topLine = String.format("+%s+", bottomTopLine); + String bottomLine = String.format("+%s+", bottomTopLine); + + // create box + List box = new ArrayList<>(wrappedLines.size() + 2); + box.add(topLine); + + for (String line : wrappedLines) { + int space = totalWidth - line.length(); + + // center line + String leftPadding, rightPadding; + if (space % 2 == 0) { + leftPadding = rightPadding = repeat(' ', space / 2); + } else { + leftPadding = repeat(' ', space / 2 + 1); + rightPadding = repeat(' ', space / 2); + } + + box.add(String.format("|%s%s%s|", leftPadding, line, rightPadding)); + } + + box.add(bottomLine); + return box; + } + + private static String repeat(char character, int length) { + char[] string = new char[length]; + Arrays.fill(string, character); + return new String(string); + } +} diff --git a/zip-plugin/src/main/resources/lang/en_US.yml b/zip-plugin/src/main/resources/lang/en_US.yml index 22be703..5190ed2 100644 --- a/zip-plugin/src/main/resources/lang/en_US.yml +++ b/zip-plugin/src/main/resources/lang/en_US.yml @@ -45,10 +45,11 @@ youNeedToLinkABackpackFirst: "You need to link a backpack at first" yourBackpackLinkRequestWasCancelled: "Your backpack link request was cancelled" bothBackpacksNeedToBeTheSameType: "Both Backpacks need to be the same type" thisBackpackIsAlreadyLinkedThoThat: "This backpack is already linked to that backpack" +stackedBackpacksCanNotBeLinked: "Stacked backpacks can not be linked at the same time" pleaseEnterANumber: "Please enter a number" enterANumberBetweenArgsAndArgs: "Please enter a number between {0} and {1}" loreLineCreate: "The lore line {0} was added" loreLineChange: "The lore line {0} was changed" loreLineDelete: "The lore line {0} was deleted" maxLoreCountReached: "You have reached the max lore count of {0}" -unableToLoadBackpack: "Backpack can't be loaded!" \ No newline at end of file +unableToLoadBackpack: "Backpack id &8\"&e{0}&8\" &7can't be loaded!" \ No newline at end of file diff --git a/zip-plugin/src/main/resources/plugin.yml b/zip-plugin/src/main/resources/plugin.yml index e6e74b8..ea27ebc 100644 --- a/zip-plugin/src/main/resources/plugin.yml +++ b/zip-plugin/src/main/resources/plugin.yml @@ -31,6 +31,6 @@ permissions: zeroinventoryproblems.type: default: op description: A list of all backpack types - zeroinventoryproblems.lore: + zeroinventoryproblems.migrate: default: op - description: Change the lore of your current backpack \ No newline at end of file + description: Migrate all existing backpacks to the new format \ No newline at end of file