diff --git a/.github/workflows/buildtools.sh b/.github/workflows/buildtools.sh
index 29a1243..4c348b0 100644
--- a/.github/workflows/buildtools.sh
+++ b/.github/workflows/buildtools.sh
@@ -37,4 +37,5 @@ checkVersion "1.21.3" "21"
checkVersion "1.21.4" "21"
checkVersion "1.21.5" "21"
checkVersion "1.21.6" "21"
-checkVersion "1.21.9" "21"
\ No newline at end of file
+checkVersion "1.21.10" "21"
+checkVersion "1.21.11" "21"
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 4719b15..fd9968e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,6 +26,7 @@
3.10.1
3.4.0
+ 3.4.2
1.2.7
2.0.2
@@ -51,14 +52,6 @@
-
- org.apache.maven.plugins
- maven-compiler-plugin
- ${plugin.compile.version}
-
- 17
-
-
org.apache.maven.plugins
maven-shade-plugin
@@ -90,6 +83,26 @@
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ ${plugin.jar.version}
+
+
+
+ mojang
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${plugin.compile.version}
+
+ 17
+
+
org.codehaus.mojo
flatten-maven-plugin
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 590d089..2550e93 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
@@ -18,7 +18,8 @@ private static final class NmsMapping {
private static final List MAPPINGS = new ArrayList<>();
static {
- MAPPINGS.add(new NmsMapping("1.21.9", "v1_21_R6"));
+ MAPPINGS.add(new NmsMapping("1.21.11", "v1_21_R7"));
+ MAPPINGS.add(new NmsMapping("1.21.10", "v1_21_R6"));
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"));
diff --git a/zip-common/src/main/java/net/imprex/zip/common/ServerVersion.java b/zip-common/src/main/java/net/imprex/zip/common/ServerVersion.java
new file mode 100644
index 0000000..cacf07d
--- /dev/null
+++ b/zip-common/src/main/java/net/imprex/zip/common/ServerVersion.java
@@ -0,0 +1,48 @@
+/**
+ * @author Imprex-Development
+ * @see ServerVersion.java
+ */
+package net.imprex.zip.common;
+
+public class ServerVersion {
+
+ private static final boolean IS_MOJANG_MAPPED = classExists("net.minecraft.core.BlockPos")
+ && fieldExists("net.minecraft.world.level.block.Blocks", "AIR");
+ private static final boolean IS_FOLIA = classExists("io.papermc.paper.threadedregions.RegionizedServer");
+ private static final boolean IS_PAPER = !IS_FOLIA && classExists("com.destroystokyo.paper.PaperConfig");
+ private static final boolean IS_BUKKIT = !IS_FOLIA && !IS_PAPER;
+
+ private static boolean classExists(String className) {
+ try {
+ Class.forName(className);
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ private static boolean fieldExists(String className, String fieldName) {
+ try {
+ Class> target = Class.forName(className);
+ return target.getDeclaredField(fieldName) != null;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public static boolean isMojangMapped() {
+ return IS_MOJANG_MAPPED;
+ }
+
+ public static boolean isFolia() {
+ return IS_FOLIA;
+ }
+
+ public static boolean isPaper() {
+ return IS_PAPER;
+ }
+
+ public static boolean isBukkit() {
+ return IS_BUKKIT;
+ }
+}
diff --git a/zip-nms/pom.xml b/zip-nms/pom.xml
index 896168c..75ca266 100644
--- a/zip-nms/pom.xml
+++ b/zip-nms/pom.xml
@@ -25,5 +25,6 @@
zip-nms-v1_21_R4
zip-nms-v1_21_R5
zip-nms-v1_21_R6
+ zip-nms-v1_21_R7
\ No newline at end of file
diff --git a/zip-nms/zip-nms-v1_21_R7/pom.xml b/zip-nms/zip-nms-v1_21_R7/pom.xml
new file mode 100644
index 0000000..f5fa515
--- /dev/null
+++ b/zip-nms/zip-nms-v1_21_R7/pom.xml
@@ -0,0 +1,84 @@
+
+ 4.0.0
+
+
+ net.imprex
+ zip-nms
+ ${revision}
+
+
+ zip-nms-v1_21_R7
+
+
+
+ net.imprex
+ zip-nms-api
+ ${revision}
+ provided
+
+
+ org.spigotmc
+ spigot
+ 1.21.11-R0.1-SNAPSHOT
+ remapped-mojang
+ provided
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+ true
+ mojang-mapped
+
+
+ net.imprex.zip.nms.v1_21_R7
+ net.imprex.zip.nms.v1_21_R7_mojang
+
+
+
+
+
+ net.md-5
+ specialsource-maven-plugin
+ ${plugin.specialsource.version}
+
+
+ package
+
+ remap
+
+ remap-obf
+
+
+ org.spigotmc:minecraft-server:1.21.11-R0.1-SNAPSHOT:txt:maps-mojang
+ true
+
+ org.spigotmc:spigot:1.21.11-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.11-R0.1-SNAPSHOT:csrg:maps-spigot
+
+ org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT:jar:remapped-obf
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zip-nms/zip-nms-v1_21_R7/src/main/java/net/imprex/zip/nms/v1_21_R7/ZipNmsManager.java b/zip-nms/zip-nms-v1_21_R7/src/main/java/net/imprex/zip/nms/v1_21_R7/ZipNmsManager.java
new file mode 100644
index 0000000..b28bc29
--- /dev/null
+++ b/zip-nms/zip-nms-v1_21_R7/src/main/java/net/imprex/zip/nms/v1_21_R7/ZipNmsManager.java
@@ -0,0 +1,260 @@
+package net.imprex.zip.nms.v1_21_R7;
+
+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.HashMap;
+import java.util.List;
+import java.util.UUID;
+import java.util.function.BiConsumer;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.SkullMeta;
+
+import com.google.common.collect.Multimaps;
+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.authlib.properties.PropertyMap;
+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;
+
+ private static final Method CRAFT_ITEM_STACK_AS_NMS_COPY;
+ private static final Method CRAFT_ITEM_STACK_AS_CRAFT_MIRROR;
+
+ 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, ResolvableProfile.createResolved(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;
+
+ String craftItemStackClass = Bukkit.getServer().getClass().getPackageName() + ".inventory.CraftItemStack";
+ // CraftItemStack.asNMSCopy(item)
+ try {
+ Class> craftItemStack = Class.forName(craftItemStackClass);
+ Method method = ReflectionUtil.searchMethod(
+ craftItemStack,
+ net.minecraft.world.item.ItemStack.class,
+ ItemStack.class);
+ method.setAccessible(true);
+ CRAFT_ITEM_STACK_AS_NMS_COPY = method;
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+
+ // CraftItemStack.asCraftMirror(item)
+ try {
+ Class> craftItemStack = Class.forName(craftItemStackClass);
+ Method method = ReflectionUtil.searchMethod(
+ craftItemStack,
+ craftItemStack,
+ net.minecraft.world.item.ItemStack.class);
+ method.setAccessible(true);
+ CRAFT_ITEM_STACK_AS_CRAFT_MIRROR = method;
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static net.minecraft.world.item.ItemStack asNmsCopy(ItemStack item) {
+ try {
+ return (net.minecraft.world.item.ItemStack) CRAFT_ITEM_STACK_AS_NMS_COPY.invoke(null, item);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static ItemStack asCraftMirror(net.minecraft.world.item.ItemStack item) {
+ try {
+ return (ItemStack) CRAFT_ITEM_STACK_AS_CRAFT_MIRROR.invoke(null, item);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @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 = 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 = 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 {
+ HashMap properties = new HashMap<>();
+ properties.put("textures", new Property("textures", texture));
+
+ PropertyMap propertyMap = new PropertyMap(Multimaps.forMap(properties));
+ GameProfile gameProfile = new GameProfile(UUID.randomUUID(), "", propertyMap);
+
+ 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 cd7515d..38f6ca5 100644
--- a/zip-plugin/pom.xml
+++ b/zip-plugin/pom.xml
@@ -125,5 +125,18 @@
${revision}
compile
+
+ net.imprex
+ zip-nms-v1_21_R7
+ ${revision}
+ compile
+
+
+ net.imprex
+ zip-nms-v1_21_R7
+ ${revision}
+ mojang-mapped
+ compile
+
\ No newline at end of file
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 1232970..4e8abd1 100644
--- a/zip-plugin/src/main/java/net/imprex/zip/NmsInstance.java
+++ b/zip-plugin/src/main/java/net/imprex/zip/NmsInstance.java
@@ -10,20 +10,26 @@
import com.google.gson.JsonObject;
import net.imprex.zip.common.MinecraftVersion;
+import net.imprex.zip.common.ServerVersion;
import net.imprex.zip.common.ZIPLogger;
import net.imprex.zip.nms.api.ItemStackContainerResult;
import net.imprex.zip.nms.api.NmsManager;
public class NmsInstance {
-
+
private static NmsManager instance;
public static void initialize() {
+
if (NmsInstance.instance != null) {
throw new IllegalStateException("NMS adapter is already initialized!");
}
String nmsVersion = MinecraftVersion.nmsVersion();
+ if (ServerVersion.isMojangMapped()) {
+ nmsVersion += "_mojang";
+ }
+
ZIPLogger.info("Searching NMS adapter for server version \"" + nmsVersion + "\"!");
try {