From c8668f6206b0e5f62f8a798ed0cbf081d455c76e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Jul 2026 20:12:36 +0000
Subject: [PATCH 1/6] Initial plan
From 7a55fed5f2e9baf9f899b92ddcbd7930f771feea Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Jul 2026 20:14:38 +0000
Subject: [PATCH 2/6] fix: support Purpur 26.1.2 in version mapping
---
.../magemonkey/codex/compat/VersionManager.java | 2 +-
.../studio/magemonkey/codex/core/Version.java | 2 +-
.../codex/compat/VersionManagerTest.java | 17 +++++++++++++++++
3 files changed, 19 insertions(+), 2 deletions(-)
create mode 100644 codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java
diff --git a/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java b/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java
index c03cbb9e..e0650c24 100644
--- a/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java
+++ b/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java
@@ -67,7 +67,7 @@ private static String getPackageFromVersion(String version) {
case "1.21.6", "1.21.7", "1.21.8" -> "v1_21_7";
case "1.21.9", "1.21.10" -> "v1_21_10";
case "1.21.11" -> "v1_21_11";
- case "26.2" -> "v26_2";
+ case "26.1.2", "26.2" -> "v26_2";
default -> throw new UnsupportedVersionException("Unknown version " + version);
};
}
diff --git a/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java b/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java
index 7e8cfa30..26534051 100644
--- a/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java
+++ b/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java
@@ -56,7 +56,7 @@ else if (versionRaw.equals("craftbukkit")) {
case "1.21.6-R0.1-SNAPSHOT", "1.21.7-R0.1-SNAPSHOT", "1.21.8-R0.1-SNAPSHOT" -> Version.V1_21_R5;
case "1.21.9-R0.1-SNAPSHOT", "1.21.10-R0.1-SNAPSHOT" -> Version.V1_21_R6;
case "1.21.11-R0.1-SNAPSHOT" -> Version.V1_21_R7;
- case "26.2-R0.1-SNAPSHOT", "26.2" -> Version.V26_R1;
+ case "26.1.2-R0.1-SNAPSHOT", "26.1.2", "26.2-R0.1-SNAPSHOT", "26.2" -> Version.V26_R1;
default -> throw new IllegalStateException("Unexpected version: " + version);
};
} else
diff --git a/codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java b/codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java
new file mode 100644
index 00000000..abb4ca81
--- /dev/null
+++ b/codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java
@@ -0,0 +1,17 @@
+package studio.magemonkey.codex.compat;
+
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+
+class VersionManagerTest {
+
+ @Test
+ void getPackageFromVersion_supportsPurpur2612() throws Exception {
+ Method method = VersionManager.class.getDeclaredMethod("getPackageFromVersion", String.class);
+ method.setAccessible(true);
+
+ Object packageName = method.invoke(null, "26.1.2");
+ assert packageName.equals("v26_2");
+ }
+}
From fbf1ba69f7525bfedbb6ff29acf9097e9ac825c4 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Wed, 8 Jul 2026 23:01:19 +0200
Subject: [PATCH 3/6] split 26_1 and 26_2
---
.../magemonkey/codex/compat/VersionManager.java | 3 ++-
.../studio/magemonkey/codex/core/Version.java | 6 ++++--
.../codex/compat/VersionManagerTest.java | 17 -----------------
3 files changed, 6 insertions(+), 20 deletions(-)
delete mode 100644 codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java
diff --git a/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java b/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java
index e0650c24..144db875 100644
--- a/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java
+++ b/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java
@@ -67,7 +67,8 @@ private static String getPackageFromVersion(String version) {
case "1.21.6", "1.21.7", "1.21.8" -> "v1_21_7";
case "1.21.9", "1.21.10" -> "v1_21_10";
case "1.21.11" -> "v1_21_11";
- case "26.1.2", "26.2" -> "v26_2";
+ case "26.1.2" -> "v26_1";
+ case "26.2" -> "v26_2";
default -> throw new UnsupportedVersionException("Unknown version " + version);
};
}
diff --git a/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java b/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java
index 26534051..b450de19 100644
--- a/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java
+++ b/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java
@@ -26,7 +26,8 @@ public enum Version {
V1_21_R5,
V1_21_R6,
V1_21_R7,
- V26_R1;
+ V26_R1,
+ V26_R2;
public static final Version CURRENT;
@@ -56,7 +57,8 @@ else if (versionRaw.equals("craftbukkit")) {
case "1.21.6-R0.1-SNAPSHOT", "1.21.7-R0.1-SNAPSHOT", "1.21.8-R0.1-SNAPSHOT" -> Version.V1_21_R5;
case "1.21.9-R0.1-SNAPSHOT", "1.21.10-R0.1-SNAPSHOT" -> Version.V1_21_R6;
case "1.21.11-R0.1-SNAPSHOT" -> Version.V1_21_R7;
- case "26.1.2-R0.1-SNAPSHOT", "26.1.2", "26.2-R0.1-SNAPSHOT", "26.2" -> Version.V26_R1;
+ case "26.1.2-R0.1-SNAPSHOT", "26.1.2" -> Version.V26_R1;
+ case "26.2-R0.1-SNAPSHOT", "26.2" -> Version.V26_R2;
default -> throw new IllegalStateException("Unexpected version: " + version);
};
} else
diff --git a/codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java b/codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java
deleted file mode 100644
index abb4ca81..00000000
--- a/codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package studio.magemonkey.codex.compat;
-
-import org.junit.jupiter.api.Test;
-
-import java.lang.reflect.Method;
-
-class VersionManagerTest {
-
- @Test
- void getPackageFromVersion_supportsPurpur2612() throws Exception {
- Method method = VersionManager.class.getDeclaredMethod("getPackageFromVersion", String.class);
- method.setAccessible(true);
-
- Object packageName = method.invoke(null, "26.1.2");
- assert packageName.equals("v26_2");
- }
-}
From 6473315ee2cb61162c3f7d89d2b4b40dde8f1673 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Wed, 8 Jul 2026 23:55:52 +0200
Subject: [PATCH 4/6] nms package vor 26_1
---
codex-core/pom.xml | 5 +
codex-nms/codex-nms-v26_1/pom.xml | 61 ++++
.../codex/nms/v26_1/ArmorUtilImpl.java | 72 ++++
.../codex/nms/v26_1/CompatImpl.java | 100 +++++
.../magemonkey/codex/nms/v26_1/NMSImpl.java | 342 ++++++++++++++++++
codex-nms/pom.xml | 1 +
6 files changed, 581 insertions(+)
create mode 100644 codex-nms/codex-nms-v26_1/pom.xml
create mode 100644 codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/ArmorUtilImpl.java
create mode 100644 codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/CompatImpl.java
create mode 100644 codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/NMSImpl.java
diff --git a/codex-core/pom.xml b/codex-core/pom.xml
index 6cc3bee9..44522d82 100644
--- a/codex-core/pom.xml
+++ b/codex-core/pom.xml
@@ -101,6 +101,11 @@
codex-nms-v1_21_11
${nms.version}
+
+ studio.magemonkey
+ codex-nms-v26_1
+ ${nms.version}
+
studio.magemonkey
codex-nms-v26_2
diff --git a/codex-nms/codex-nms-v26_1/pom.xml b/codex-nms/codex-nms-v26_1/pom.xml
new file mode 100644
index 00000000..648cd35b
--- /dev/null
+++ b/codex-nms/codex-nms-v26_1/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+
+ studio.magemonkey
+ codex-nms
+ 1.2.0-R0.1-SNAPSHOT
+
+
+ codex-nms-v26_1
+ CodexNMS-26.1
+ 26.1 NMS utilities for Codex
+
+
+ 21
+
+
+
+
+ org.spigotmc
+ spigot-api
+ 26.1.2-R0.1-SNAPSHOT
+ provided
+
+
+ org.spigotmc
+ spigot
+ 26.1.2-R0.1-SNAPSHOT
+ provided
+
+
+ it.unimi.dsi
+ fastutil
+ 8.5.18
+ provided
+
+
+
+
+
+ MIT License
+ https://www.opensource.org/licenses/mit-license.php
+
+
+
+
+
+ VoidEdge
+ VoidEdge
+ https://void.travja.dev
+
+
+
+
+ scm:git:git://github.com/magemonkeystudio/codex.git
+ scm:git:ssh://github.com:magemonkeystudio/codex.git
+ https://github.com/magemonkeystudio/codex
+
+
diff --git a/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/ArmorUtilImpl.java b/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/ArmorUtilImpl.java
new file mode 100644
index 00000000..74f5777c
--- /dev/null
+++ b/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/ArmorUtilImpl.java
@@ -0,0 +1,72 @@
+package studio.magemonkey.codex.nms.v26_1;
+
+import org.bukkit.NamespacedKey;
+import org.bukkit.Registry;
+import org.bukkit.inventory.meta.ArmorMeta;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.inventory.meta.trim.ArmorTrim;
+import org.bukkit.inventory.meta.trim.TrimMaterial;
+import org.bukkit.inventory.meta.trim.TrimPattern;
+import studio.magemonkey.codex.compat.ArmorUtil;
+import studio.magemonkey.codex.util.random.Rnd;
+
+import java.util.Objects;
+
+@SuppressWarnings("UnstableApiUsage")
+public class ArmorUtilImpl implements ArmorUtil {
+ @Override
+ public Object getTrimMaterial(NamespacedKey key) {
+ return Registry.TRIM_MATERIAL.get(key);
+ }
+
+ @Override
+ public Object getTrimPattern(NamespacedKey key) {
+ return Registry.TRIM_PATTERN.get(key);
+ }
+
+ @Override
+ public void addTrim(ItemMeta meta, String material, String pattern) {
+ if (!(meta instanceof ArmorMeta)) return;
+ ArmorMeta armorMeta = (ArmorMeta) meta;
+
+ TrimMaterial trimMaterial = null;
+ if (material.equals("*")) {
+ int size = 0;
+ for (TrimMaterial ignored : Registry.TRIM_MATERIAL) {
+ size++;
+ }
+ int index = Rnd.get(size);
+ int i = 0;
+ for (TrimMaterial next : Registry.TRIM_MATERIAL) {
+ if (index == i) {
+ trimMaterial = next;
+ break;
+ }
+ i++;
+ }
+ } else {
+ trimMaterial = Registry.TRIM_MATERIAL.get(NamespacedKey.minecraft(material));
+ }
+
+ TrimPattern trimPattern = null;
+ if (pattern.equals("*")) {
+ int size = 0;
+ for (TrimPattern ignored : Registry.TRIM_PATTERN) size++;
+
+ int index = Rnd.get(size);
+ int i = 0;
+ for (TrimPattern next : Registry.TRIM_PATTERN) {
+ if (index == i++) {
+ trimPattern = next;
+ break;
+ }
+ }
+ } else {
+ trimPattern = Registry.TRIM_PATTERN.get(NamespacedKey.minecraft(pattern));
+ }
+
+ ArmorTrim armorTrim =
+ new ArmorTrim(Objects.requireNonNull(trimMaterial), Objects.requireNonNull(trimPattern));
+ armorMeta.setTrim(armorTrim);
+ }
+}
diff --git a/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/CompatImpl.java b/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/CompatImpl.java
new file mode 100644
index 00000000..8441b7ef
--- /dev/null
+++ b/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/CompatImpl.java
@@ -0,0 +1,100 @@
+package studio.magemonkey.codex.nms.v26_1;
+
+import net.minecraft.locale.Language;
+import org.bukkit.attribute.AttributeModifier;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryEvent;
+import org.bukkit.inventory.EquipmentSlotGroup;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryView;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.jetbrains.annotations.NotNull;
+import studio.magemonkey.codex.api.meta.NBTAttribute;
+import studio.magemonkey.codex.compat.Compat;
+
+public class CompatImpl implements Compat {
+ @Override
+ @SuppressWarnings("UnstableApiUsage")
+ public AttributeModifier createAttributeModifier(NBTAttribute attribute,
+ double amount,
+ AttributeModifier.Operation operation) {
+ return new AttributeModifier(attribute.getAttribute().getKey(), amount, operation, EquipmentSlotGroup.ANY);
+ }
+
+ @Override
+ public String getAttributeKey(AttributeModifier attributeModifier) {
+ return attributeModifier.getKey().toString();
+ }
+
+ @Override
+ public String getAttributeKey(NBTAttribute attribute) {
+ return attribute.getAttribute().getKey().toString();
+ }
+
+ @Override
+ public Inventory getTopInventory(InventoryEvent event) {
+ InventoryView view = event.getView();
+ return view.getTopInventory();
+ }
+
+ @Override
+ @NotNull
+ public Inventory getTopInventory(Player player) {
+ InventoryView view = player.getOpenInventory();
+ return view.getTopInventory();
+ }
+
+ @Override
+ public Inventory getBottomInventory(InventoryEvent event) {
+ InventoryView view = event.getView();
+ return view.getBottomInventory();
+ }
+
+ @Override
+ public void setCursor(InventoryEvent event, ItemStack item) {
+ InventoryView view = event.getView();
+ view.setCursor(item);
+ }
+
+ @Override
+ public void setItem(Player player, int slot, ItemStack item) {
+ InventoryView view = player.getOpenInventory();
+ view.setItem(slot, item);
+ }
+
+ @Override
+ public Inventory getInventory(InventoryEvent event, int slot) {
+ InventoryView view = event.getView();
+ return view.getInventory(slot);
+ }
+
+ @Override
+ public ItemStack getItem(InventoryEvent event, int slot) {
+ InventoryView view = event.getView();
+ return view.getItem(slot);
+ }
+
+ @Override
+ public int convertSlot(InventoryEvent event, int slot) {
+ InventoryView view = event.getView();
+ return view.convertSlot(slot);
+ }
+
+ @Override
+ public String getItemName(ItemStack item) {
+ ItemMeta meta = item.getItemMeta();
+ if (meta == null) {
+ return item.getType().toString();
+ }
+
+ String name = null;
+
+ if (meta.hasDisplayName()) name = meta.getDisplayName();
+ if (name == null && meta.hasItemName()) name = meta.getItemName();
+ if (name == null && meta.getLore() != null && !meta.getLore().isEmpty()) name = meta.getLore().get(0);
+ if (name == null) name = Language.getInstance().getOrDefault(item.getTranslationKey());
+
+ return name == null ? item.getType().toString() : name;
+ }
+}
diff --git a/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/NMSImpl.java b/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/NMSImpl.java
new file mode 100644
index 00000000..953b89fc
--- /dev/null
+++ b/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/NMSImpl.java
@@ -0,0 +1,342 @@
+package studio.magemonkey.codex.nms.v26_1;
+
+import com.google.common.base.Preconditions;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import io.netty.channel.Channel;
+import net.md_5.bungee.api.chat.BaseComponent;
+import net.md_5.bungee.api.chat.HoverEvent;
+import net.md_5.bungee.api.chat.TextComponent;
+import net.md_5.bungee.api.chat.TranslatableComponent;
+import net.md_5.bungee.api.chat.hover.content.Content;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.Connection;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.protocol.Packet;
+import net.minecraft.network.protocol.game.ClientboundAnimatePacket;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.network.ServerGamePacketListenerImpl;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.AxeItem;
+import net.minecraft.world.item.TridentItem;
+import net.minecraft.world.level.block.state.BlockState;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Registry;
+import org.bukkit.attribute.Attribute;
+import org.bukkit.block.Block;
+import org.bukkit.block.Skull;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.craftbukkit.entity.CraftLivingEntity;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.craftbukkit.util.CraftChatMessage;
+import org.bukkit.damage.DamageSource;
+import org.bukkit.damage.DamageType;
+import org.bukkit.entity.Boat;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ArmorMeta;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.inventory.meta.SkullMeta;
+import org.bukkit.profile.PlayerProfile;
+import org.bukkit.profile.PlayerTextures;
+import org.bukkit.scoreboard.Objective;
+import org.bukkit.scoreboard.Scoreboard;
+import org.jetbrains.annotations.NotNull;
+import studio.magemonkey.codex.Codex;
+import studio.magemonkey.codex.compat.NMS;
+
+import java.lang.reflect.Field;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Locale;
+import java.util.UUID;
+
+public class NMSImpl implements NMS {
+ @Override
+ public String getVersion() {
+ return "26.2";
+ }
+
+ @NotNull
+ @Override
+ public Object getConnection(Player player) {
+ return ((CraftPlayer) player).getHandle().connection;
+ }
+
+ @NotNull
+ @Override
+ public Channel getChannel(@NotNull Player player) {
+ ServerGamePacketListenerImpl connection = ((ServerGamePacketListenerImpl) getConnection(player));
+ // We have to reflexively get the `channel` field
+ try {
+ Field networkManagerField = getField(connection.getClass(), "connection");
+ networkManagerField.setAccessible(true);
+ Connection networkManager = (Connection) networkManagerField.get(connection);
+ return networkManager.channel;
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ throw new RuntimeException("Could not get Channel field from PlayerConnection", e);
+ }
+ }
+
+ @Override
+ public void sendPacket(@NotNull Player player, @NotNull Object packet) {
+ Preconditions.checkArgument(packet instanceof Packet,
+ "Packet must be an instance of net.minecraft.server.Packet");
+ ((ServerGamePacketListenerImpl) getConnection(player)).send((Packet>) packet);
+ }
+
+ @Override
+ public void openChestAnimation(@NotNull Block chest, boolean open) {
+ ServerLevel world = ((CraftWorld) chest.getWorld()).getHandle();
+ BlockPos position = new BlockPos(chest.getX(), chest.getY(), chest.getZ());
+ BlockState blockData = world.getBlockState(position);
+ world.blockEvent(position, blockData.getBlock(), 1, open ? 1 : 0);
+ }
+
+ @Override
+ public void sendAttackPacket(@NotNull Player player, int i) {
+ ClientboundAnimatePacket packet = new ClientboundAnimatePacket(((CraftPlayer) player).getHandle(), i);
+ sendPacket(player, packet);
+ }
+
+ @NotNull
+ @Override
+ public String fixColors(@NotNull String str) {
+ str = str.replace("\n", "%n%");
+
+ Component baseComponent = CraftChatMessage.fromStringOrNull(str);
+ String singleColor = CraftChatMessage.fromComponent(baseComponent);
+
+ return singleColor.replace("%n%", "\n");
+ }
+
+ @Override
+ public double getDefaultDamage(@NotNull ItemStack itemStack) {
+ return getAttributeValue(itemStack, Attribute.ATTACK_DAMAGE);
+ }
+
+ @Override
+ public double getDefaultSpeed(@NotNull ItemStack itemStack) {
+ return getAttributeValue(itemStack, Attribute.ATTACK_SPEED);
+ }
+
+ @Override
+ public double getDefaultArmor(@NotNull ItemStack itemStack) {
+ return getAttributeValue(itemStack, Attribute.ARMOR);
+ }
+
+ @Override
+ public double getDefaultToughness(@NotNull ItemStack itemStack) {
+ return getAttributeValue(itemStack, Attribute.ARMOR_TOUGHNESS);
+ }
+
+ @Override
+ public boolean isWeapon(@NotNull ItemStack itemStack) {
+ net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(itemStack);
+ Item item = nmsItem.getItem();
+ return itemStack.getType().name().contains("SWORD") || item instanceof AxeItem || item instanceof TridentItem;
+ }
+
+ @Override
+ public boolean isArmor(@NotNull ItemStack itemStack) {
+ return itemStack.getItemMeta() instanceof ArmorMeta;
+ }
+
+ @Override
+ public boolean isTool(@NotNull ItemStack itemStack) {
+ Material material = itemStack.getType();
+ String[] suffixes = {"_AXE", "_HOE", "_SHOVEL", "_PICKAXE", "_SHEARS"};
+ return Arrays.stream(suffixes).anyMatch(suffix -> material.name().endsWith(suffix));
+ }
+
+ @Override
+ public String toJson(@NotNull ItemStack item) {
+ return "unsupported in 26.2";
+ }
+
+ @Override
+ public void setKiller(@NotNull LivingEntity entity, @NotNull Player killer) {
+ net.minecraft.world.entity.LivingEntity hit = ((CraftLivingEntity) entity).getHandle();
+ hit.setLastHurtByPlayer(((CraftPlayer) killer).getHandle(), 100);
+ }
+
+ @Override
+ public void changeSkull(@NotNull Block block, @NotNull String hash) {
+ try {
+ if (!(block.getState() instanceof Skull)) return;
+
+ Skull skull = (Skull) block.getState();
+ PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID(), hash.substring(0, 16));
+ PlayerTextures textures = profile.getTextures();
+
+ // If the hash is a URL, we can just set the skin directly, otherwise, we need to decode
+ // the hash from Base64 and extract the url from the JSON data.
+ if (hash.startsWith("http")) {
+ textures.setSkin(new URL(hash));
+ } else {
+ String decoded;
+ try {
+ decoded = new String(Base64.getDecoder().decode(hash));
+ } catch (IllegalArgumentException ignored) {
+ try {
+ decoded = new String(Base64.getMimeDecoder().decode(hash));
+ } catch (IllegalArgumentException ignored2) {
+ decoded = new String(Base64.getUrlDecoder().decode(hash));
+ // If we throw again... we'll just break out
+ }
+ }
+ // Construct the json object
+ JsonObject json = new Gson().fromJson(decoded, JsonObject.class);
+ // Get the textures object
+ JsonObject texturesJson = json.getAsJsonObject("textures");
+ // Get the skin object
+ JsonObject skin = texturesJson.getAsJsonObject("SKIN");
+ // Get the url
+ String url = skin.get("url").getAsString();
+ textures.setSkin(new URL(url));
+ }
+
+ profile = profile.update().get();
+ skull.setOwnerProfile(profile);
+ skull.update();
+ } catch (Exception e) {
+ Codex.warn("Could not update skull");
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void addSkullTexture(@NotNull ItemStack item, @NotNull String value, @NotNull UUID uuid) {
+ if (item.getType() != Material.PLAYER_HEAD) return;
+
+ SkullMeta meta = (SkullMeta) item.getItemMeta();
+ if (meta == null) return;
+
+ try {
+ PlayerProfile playerProfile = Bukkit.createPlayerProfile(uuid, uuid.toString().substring(0, 16));
+ String decoded = new String(Base64.getDecoder().decode(value));
+ JsonObject json = new Gson().fromJson(decoded, JsonObject.class);
+ JsonObject texturesJson = json.getAsJsonObject("textures");
+ JsonObject skin = texturesJson.getAsJsonObject("SKIN");
+ String url = skin.get("url").getAsString();
+ playerProfile.getTextures().setSkin(new URL(url));
+ meta.setOwnerProfile(playerProfile);
+ item.setItemMeta(meta);
+ } catch (MalformedURLException | NoClassDefFoundError | NoSuchMethodError | IllegalArgumentException e) {
+ NMS.super.addSkullTexture(item, value, uuid);
+ }
+ }
+
+ @NotNull
+ @Override
+ public Attribute getAttribute(String name) {
+ return Registry.ATTRIBUTE.getOrThrow(NamespacedKey.minecraft(name.toLowerCase(Locale.US)));
+ }
+
+ @Override
+ public Object getNMSCopy(@NotNull ItemStack item) {
+ return CraftItemStack.asNMSCopy(item);
+ }
+
+ @Override
+ public Material getMaterial(Boat boat) {
+ String boatClassName = boat.getClass().getSimpleName().replace("Craft", "");
+ // Split on capital letters and join with underscores
+ // ex: AcaciaBoat -> ACACIA_BOAT
+ String materialName = String.join("_", boatClassName.split("(?=[A-Z])")).toUpperCase(Locale.US);
+ Material mat = Material.matchMaterial(materialName);
+
+ if (mat == null) mat = Material.OAK_BOAT;
+
+ return mat;
+ }
+
+ @Override
+ public Objective registerNewObjective(Scoreboard scoreboard, Objective objective) {
+ return scoreboard.registerNewObjective(objective.getName(),
+ objective.getTrackedCriteria(),
+ objective.getDisplayName());
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public HoverEvent getHoverEvent(@NotNull ItemStack itemStack) {
+ String components = itemStack.getItemMeta() != null ? itemStack.getItemMeta().getAsString() : "{}";
+ components = components.replaceAll(": ?0b", ": false")
+ .replaceAll(": ?1b", ": true")
+ .replaceAll(": ?(\\d+\\.\\d+)d", ": $1")
+ .replaceAll("\\b(\\d+\\.\\d+)f\\b", "$1");
+ return new HoverEvent(HoverEvent.Action.SHOW_ITEM,
+ new ComponentsShowItem(
+ itemStack.getType().getKey().toString(),
+ itemStack.getAmount(),
+ new Gson().fromJson(components, JsonObject.class))
+ );
+ }
+
+ /**
+ * Minimal show_item content carrying components for modern clients.
+ */
+ private static final class ComponentsShowItem extends Content {
+ private final String id;
+ private final int count;
+ private final JsonElement components;
+
+ private ComponentsShowItem(final String id, final int count, final JsonElement components) {
+ this.id = id;
+ this.count = count;
+ this.components = components;
+ }
+
+ @Override
+ public HoverEvent.Action requiredAction() {
+ return HoverEvent.Action.SHOW_ITEM;
+ }
+ }
+
+ @Override
+ public BaseComponent getTranslatedComponent(@NotNull ItemStack itemStack) {
+ ItemMeta meta = itemStack.getItemMeta();
+ String string = null;
+ if (meta != null) {
+ string = meta.getDisplayName();
+ if (string.isEmpty()) {
+ try {
+ string = meta.getItemName();
+ } catch (NoSuchMethodError ignored) {
+ }
+ }
+ }
+
+ BaseComponent baseComponent;
+ if (string == null || string.isEmpty()) {
+ baseComponent = new TranslatableComponent(itemStack.getType().getItemTranslationKey());
+ } else {
+ baseComponent = new TextComponent(string);
+ }
+
+ return baseComponent;
+ }
+
+ @Override
+ @SuppressWarnings("UnstableApiUsage")
+ public EntityDamageByEntityEvent createEntityDamageEvent(@NotNull Entity entity,
+ @NotNull Entity attacker,
+ @NotNull EntityDamageByEntityEvent.DamageCause cause,
+ double damage) {
+ return new EntityDamageByEntityEvent(attacker,
+ entity,
+ cause,
+ DamageSource.builder(DamageType.MOB_ATTACK).build(),
+ damage);
+ }
+}
diff --git a/codex-nms/pom.xml b/codex-nms/pom.xml
index 436559cc..d37a2865 100644
--- a/codex-nms/pom.xml
+++ b/codex-nms/pom.xml
@@ -43,6 +43,7 @@
codex-nms-v1_21_7
codex-nms-v1_21_10
codex-nms-v1_21_11
+ codex-nms-v26_1
codex-nms-v26_2
From 739ee42f494c8448d00dca915bfe2ce0fc04fb99 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Wed, 8 Jul 2026 23:56:00 +0200
Subject: [PATCH 5/6] build issue fix
---
codex-nms/codex-nms-v1_21_11/pom.xml | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/codex-nms/codex-nms-v1_21_11/pom.xml b/codex-nms/codex-nms-v1_21_11/pom.xml
index b070547b..65f8699b 100644
--- a/codex-nms/codex-nms-v1_21_11/pom.xml
+++ b/codex-nms/codex-nms-v1_21_11/pom.xml
@@ -37,6 +37,18 @@
8.5.18
provided
+
+ com.mojang
+ datafixerupper
+ 9.0.19
+ provided
+
+
+ com.mojang
+ brigadier
+ 1.3.10
+ provided
+
From 0d90340410da312166398915ac9a73375132352f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 9 Jul 2026 16:58:16 +0000
Subject: [PATCH 6/6] revert: undo requested commits fbf1ba6 6473315 739ee42
---
.../codex/compat/VersionManager.java | 3 +-
.../studio/magemonkey/codex/core/Version.java | 6 +-
.../codex/compat/VersionManagerTest.java | 17 +
codex-core/pom.xml | 5 -
codex-nms/codex-nms-v1_21_11/pom.xml | 12 -
codex-nms/codex-nms-v26_1/pom.xml | 61 ----
.../codex/nms/v26_1/ArmorUtilImpl.java | 72 ----
.../codex/nms/v26_1/CompatImpl.java | 100 -----
.../magemonkey/codex/nms/v26_1/NMSImpl.java | 342 ------------------
codex-nms/pom.xml | 1 -
10 files changed, 20 insertions(+), 599 deletions(-)
create mode 100644 codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java
delete mode 100644 codex-nms/codex-nms-v26_1/pom.xml
delete mode 100644 codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/ArmorUtilImpl.java
delete mode 100644 codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/CompatImpl.java
delete mode 100644 codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/NMSImpl.java
diff --git a/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java b/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java
index 144db875..e0650c24 100644
--- a/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java
+++ b/codex-api/src/main/java/studio/magemonkey/codex/compat/VersionManager.java
@@ -67,8 +67,7 @@ private static String getPackageFromVersion(String version) {
case "1.21.6", "1.21.7", "1.21.8" -> "v1_21_7";
case "1.21.9", "1.21.10" -> "v1_21_10";
case "1.21.11" -> "v1_21_11";
- case "26.1.2" -> "v26_1";
- case "26.2" -> "v26_2";
+ case "26.1.2", "26.2" -> "v26_2";
default -> throw new UnsupportedVersionException("Unknown version " + version);
};
}
diff --git a/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java b/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java
index b450de19..26534051 100644
--- a/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java
+++ b/codex-api/src/main/java/studio/magemonkey/codex/core/Version.java
@@ -26,8 +26,7 @@ public enum Version {
V1_21_R5,
V1_21_R6,
V1_21_R7,
- V26_R1,
- V26_R2;
+ V26_R1;
public static final Version CURRENT;
@@ -57,8 +56,7 @@ else if (versionRaw.equals("craftbukkit")) {
case "1.21.6-R0.1-SNAPSHOT", "1.21.7-R0.1-SNAPSHOT", "1.21.8-R0.1-SNAPSHOT" -> Version.V1_21_R5;
case "1.21.9-R0.1-SNAPSHOT", "1.21.10-R0.1-SNAPSHOT" -> Version.V1_21_R6;
case "1.21.11-R0.1-SNAPSHOT" -> Version.V1_21_R7;
- case "26.1.2-R0.1-SNAPSHOT", "26.1.2" -> Version.V26_R1;
- case "26.2-R0.1-SNAPSHOT", "26.2" -> Version.V26_R2;
+ case "26.1.2-R0.1-SNAPSHOT", "26.1.2", "26.2-R0.1-SNAPSHOT", "26.2" -> Version.V26_R1;
default -> throw new IllegalStateException("Unexpected version: " + version);
};
} else
diff --git a/codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java b/codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java
new file mode 100644
index 00000000..abb4ca81
--- /dev/null
+++ b/codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java
@@ -0,0 +1,17 @@
+package studio.magemonkey.codex.compat;
+
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+
+class VersionManagerTest {
+
+ @Test
+ void getPackageFromVersion_supportsPurpur2612() throws Exception {
+ Method method = VersionManager.class.getDeclaredMethod("getPackageFromVersion", String.class);
+ method.setAccessible(true);
+
+ Object packageName = method.invoke(null, "26.1.2");
+ assert packageName.equals("v26_2");
+ }
+}
diff --git a/codex-core/pom.xml b/codex-core/pom.xml
index 44522d82..6cc3bee9 100644
--- a/codex-core/pom.xml
+++ b/codex-core/pom.xml
@@ -101,11 +101,6 @@
codex-nms-v1_21_11
${nms.version}
-
- studio.magemonkey
- codex-nms-v26_1
- ${nms.version}
-
studio.magemonkey
codex-nms-v26_2
diff --git a/codex-nms/codex-nms-v1_21_11/pom.xml b/codex-nms/codex-nms-v1_21_11/pom.xml
index 65f8699b..b070547b 100644
--- a/codex-nms/codex-nms-v1_21_11/pom.xml
+++ b/codex-nms/codex-nms-v1_21_11/pom.xml
@@ -37,18 +37,6 @@
8.5.18
provided
-
- com.mojang
- datafixerupper
- 9.0.19
- provided
-
-
- com.mojang
- brigadier
- 1.3.10
- provided
-
diff --git a/codex-nms/codex-nms-v26_1/pom.xml b/codex-nms/codex-nms-v26_1/pom.xml
deleted file mode 100644
index 648cd35b..00000000
--- a/codex-nms/codex-nms-v26_1/pom.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
- 4.0.0
-
- studio.magemonkey
- codex-nms
- 1.2.0-R0.1-SNAPSHOT
-
-
- codex-nms-v26_1
- CodexNMS-26.1
- 26.1 NMS utilities for Codex
-
-
- 21
-
-
-
-
- org.spigotmc
- spigot-api
- 26.1.2-R0.1-SNAPSHOT
- provided
-
-
- org.spigotmc
- spigot
- 26.1.2-R0.1-SNAPSHOT
- provided
-
-
- it.unimi.dsi
- fastutil
- 8.5.18
- provided
-
-
-
-
-
- MIT License
- https://www.opensource.org/licenses/mit-license.php
-
-
-
-
-
- VoidEdge
- VoidEdge
- https://void.travja.dev
-
-
-
-
- scm:git:git://github.com/magemonkeystudio/codex.git
- scm:git:ssh://github.com:magemonkeystudio/codex.git
- https://github.com/magemonkeystudio/codex
-
-
diff --git a/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/ArmorUtilImpl.java b/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/ArmorUtilImpl.java
deleted file mode 100644
index 74f5777c..00000000
--- a/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/ArmorUtilImpl.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package studio.magemonkey.codex.nms.v26_1;
-
-import org.bukkit.NamespacedKey;
-import org.bukkit.Registry;
-import org.bukkit.inventory.meta.ArmorMeta;
-import org.bukkit.inventory.meta.ItemMeta;
-import org.bukkit.inventory.meta.trim.ArmorTrim;
-import org.bukkit.inventory.meta.trim.TrimMaterial;
-import org.bukkit.inventory.meta.trim.TrimPattern;
-import studio.magemonkey.codex.compat.ArmorUtil;
-import studio.magemonkey.codex.util.random.Rnd;
-
-import java.util.Objects;
-
-@SuppressWarnings("UnstableApiUsage")
-public class ArmorUtilImpl implements ArmorUtil {
- @Override
- public Object getTrimMaterial(NamespacedKey key) {
- return Registry.TRIM_MATERIAL.get(key);
- }
-
- @Override
- public Object getTrimPattern(NamespacedKey key) {
- return Registry.TRIM_PATTERN.get(key);
- }
-
- @Override
- public void addTrim(ItemMeta meta, String material, String pattern) {
- if (!(meta instanceof ArmorMeta)) return;
- ArmorMeta armorMeta = (ArmorMeta) meta;
-
- TrimMaterial trimMaterial = null;
- if (material.equals("*")) {
- int size = 0;
- for (TrimMaterial ignored : Registry.TRIM_MATERIAL) {
- size++;
- }
- int index = Rnd.get(size);
- int i = 0;
- for (TrimMaterial next : Registry.TRIM_MATERIAL) {
- if (index == i) {
- trimMaterial = next;
- break;
- }
- i++;
- }
- } else {
- trimMaterial = Registry.TRIM_MATERIAL.get(NamespacedKey.minecraft(material));
- }
-
- TrimPattern trimPattern = null;
- if (pattern.equals("*")) {
- int size = 0;
- for (TrimPattern ignored : Registry.TRIM_PATTERN) size++;
-
- int index = Rnd.get(size);
- int i = 0;
- for (TrimPattern next : Registry.TRIM_PATTERN) {
- if (index == i++) {
- trimPattern = next;
- break;
- }
- }
- } else {
- trimPattern = Registry.TRIM_PATTERN.get(NamespacedKey.minecraft(pattern));
- }
-
- ArmorTrim armorTrim =
- new ArmorTrim(Objects.requireNonNull(trimMaterial), Objects.requireNonNull(trimPattern));
- armorMeta.setTrim(armorTrim);
- }
-}
diff --git a/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/CompatImpl.java b/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/CompatImpl.java
deleted file mode 100644
index 8441b7ef..00000000
--- a/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/CompatImpl.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package studio.magemonkey.codex.nms.v26_1;
-
-import net.minecraft.locale.Language;
-import org.bukkit.attribute.AttributeModifier;
-import org.bukkit.entity.Player;
-import org.bukkit.event.inventory.InventoryEvent;
-import org.bukkit.inventory.EquipmentSlotGroup;
-import org.bukkit.inventory.Inventory;
-import org.bukkit.inventory.InventoryView;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.inventory.meta.ItemMeta;
-import org.jetbrains.annotations.NotNull;
-import studio.magemonkey.codex.api.meta.NBTAttribute;
-import studio.magemonkey.codex.compat.Compat;
-
-public class CompatImpl implements Compat {
- @Override
- @SuppressWarnings("UnstableApiUsage")
- public AttributeModifier createAttributeModifier(NBTAttribute attribute,
- double amount,
- AttributeModifier.Operation operation) {
- return new AttributeModifier(attribute.getAttribute().getKey(), amount, operation, EquipmentSlotGroup.ANY);
- }
-
- @Override
- public String getAttributeKey(AttributeModifier attributeModifier) {
- return attributeModifier.getKey().toString();
- }
-
- @Override
- public String getAttributeKey(NBTAttribute attribute) {
- return attribute.getAttribute().getKey().toString();
- }
-
- @Override
- public Inventory getTopInventory(InventoryEvent event) {
- InventoryView view = event.getView();
- return view.getTopInventory();
- }
-
- @Override
- @NotNull
- public Inventory getTopInventory(Player player) {
- InventoryView view = player.getOpenInventory();
- return view.getTopInventory();
- }
-
- @Override
- public Inventory getBottomInventory(InventoryEvent event) {
- InventoryView view = event.getView();
- return view.getBottomInventory();
- }
-
- @Override
- public void setCursor(InventoryEvent event, ItemStack item) {
- InventoryView view = event.getView();
- view.setCursor(item);
- }
-
- @Override
- public void setItem(Player player, int slot, ItemStack item) {
- InventoryView view = player.getOpenInventory();
- view.setItem(slot, item);
- }
-
- @Override
- public Inventory getInventory(InventoryEvent event, int slot) {
- InventoryView view = event.getView();
- return view.getInventory(slot);
- }
-
- @Override
- public ItemStack getItem(InventoryEvent event, int slot) {
- InventoryView view = event.getView();
- return view.getItem(slot);
- }
-
- @Override
- public int convertSlot(InventoryEvent event, int slot) {
- InventoryView view = event.getView();
- return view.convertSlot(slot);
- }
-
- @Override
- public String getItemName(ItemStack item) {
- ItemMeta meta = item.getItemMeta();
- if (meta == null) {
- return item.getType().toString();
- }
-
- String name = null;
-
- if (meta.hasDisplayName()) name = meta.getDisplayName();
- if (name == null && meta.hasItemName()) name = meta.getItemName();
- if (name == null && meta.getLore() != null && !meta.getLore().isEmpty()) name = meta.getLore().get(0);
- if (name == null) name = Language.getInstance().getOrDefault(item.getTranslationKey());
-
- return name == null ? item.getType().toString() : name;
- }
-}
diff --git a/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/NMSImpl.java b/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/NMSImpl.java
deleted file mode 100644
index 953b89fc..00000000
--- a/codex-nms/codex-nms-v26_1/src/main/java/studio/magemonkey/codex/nms/v26_1/NMSImpl.java
+++ /dev/null
@@ -1,342 +0,0 @@
-package studio.magemonkey.codex.nms.v26_1;
-
-import com.google.common.base.Preconditions;
-import com.google.gson.Gson;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import io.netty.channel.Channel;
-import net.md_5.bungee.api.chat.BaseComponent;
-import net.md_5.bungee.api.chat.HoverEvent;
-import net.md_5.bungee.api.chat.TextComponent;
-import net.md_5.bungee.api.chat.TranslatableComponent;
-import net.md_5.bungee.api.chat.hover.content.Content;
-import net.minecraft.core.BlockPos;
-import net.minecraft.network.Connection;
-import net.minecraft.network.chat.Component;
-import net.minecraft.network.protocol.Packet;
-import net.minecraft.network.protocol.game.ClientboundAnimatePacket;
-import net.minecraft.server.level.ServerLevel;
-import net.minecraft.server.network.ServerGamePacketListenerImpl;
-import net.minecraft.world.item.Item;
-import net.minecraft.world.item.AxeItem;
-import net.minecraft.world.item.TridentItem;
-import net.minecraft.world.level.block.state.BlockState;
-import org.bukkit.Bukkit;
-import org.bukkit.Material;
-import org.bukkit.NamespacedKey;
-import org.bukkit.Registry;
-import org.bukkit.attribute.Attribute;
-import org.bukkit.block.Block;
-import org.bukkit.block.Skull;
-import org.bukkit.craftbukkit.CraftWorld;
-import org.bukkit.craftbukkit.entity.CraftLivingEntity;
-import org.bukkit.craftbukkit.entity.CraftPlayer;
-import org.bukkit.craftbukkit.inventory.CraftItemStack;
-import org.bukkit.craftbukkit.util.CraftChatMessage;
-import org.bukkit.damage.DamageSource;
-import org.bukkit.damage.DamageType;
-import org.bukkit.entity.Boat;
-import org.bukkit.entity.Entity;
-import org.bukkit.entity.LivingEntity;
-import org.bukkit.entity.Player;
-import org.bukkit.event.entity.EntityDamageByEntityEvent;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.inventory.meta.ArmorMeta;
-import org.bukkit.inventory.meta.ItemMeta;
-import org.bukkit.inventory.meta.SkullMeta;
-import org.bukkit.profile.PlayerProfile;
-import org.bukkit.profile.PlayerTextures;
-import org.bukkit.scoreboard.Objective;
-import org.bukkit.scoreboard.Scoreboard;
-import org.jetbrains.annotations.NotNull;
-import studio.magemonkey.codex.Codex;
-import studio.magemonkey.codex.compat.NMS;
-
-import java.lang.reflect.Field;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Locale;
-import java.util.UUID;
-
-public class NMSImpl implements NMS {
- @Override
- public String getVersion() {
- return "26.2";
- }
-
- @NotNull
- @Override
- public Object getConnection(Player player) {
- return ((CraftPlayer) player).getHandle().connection;
- }
-
- @NotNull
- @Override
- public Channel getChannel(@NotNull Player player) {
- ServerGamePacketListenerImpl connection = ((ServerGamePacketListenerImpl) getConnection(player));
- // We have to reflexively get the `channel` field
- try {
- Field networkManagerField = getField(connection.getClass(), "connection");
- networkManagerField.setAccessible(true);
- Connection networkManager = (Connection) networkManagerField.get(connection);
- return networkManager.channel;
- } catch (IllegalAccessException | NoSuchFieldException e) {
- throw new RuntimeException("Could not get Channel field from PlayerConnection", e);
- }
- }
-
- @Override
- public void sendPacket(@NotNull Player player, @NotNull Object packet) {
- Preconditions.checkArgument(packet instanceof Packet,
- "Packet must be an instance of net.minecraft.server.Packet");
- ((ServerGamePacketListenerImpl) getConnection(player)).send((Packet>) packet);
- }
-
- @Override
- public void openChestAnimation(@NotNull Block chest, boolean open) {
- ServerLevel world = ((CraftWorld) chest.getWorld()).getHandle();
- BlockPos position = new BlockPos(chest.getX(), chest.getY(), chest.getZ());
- BlockState blockData = world.getBlockState(position);
- world.blockEvent(position, blockData.getBlock(), 1, open ? 1 : 0);
- }
-
- @Override
- public void sendAttackPacket(@NotNull Player player, int i) {
- ClientboundAnimatePacket packet = new ClientboundAnimatePacket(((CraftPlayer) player).getHandle(), i);
- sendPacket(player, packet);
- }
-
- @NotNull
- @Override
- public String fixColors(@NotNull String str) {
- str = str.replace("\n", "%n%");
-
- Component baseComponent = CraftChatMessage.fromStringOrNull(str);
- String singleColor = CraftChatMessage.fromComponent(baseComponent);
-
- return singleColor.replace("%n%", "\n");
- }
-
- @Override
- public double getDefaultDamage(@NotNull ItemStack itemStack) {
- return getAttributeValue(itemStack, Attribute.ATTACK_DAMAGE);
- }
-
- @Override
- public double getDefaultSpeed(@NotNull ItemStack itemStack) {
- return getAttributeValue(itemStack, Attribute.ATTACK_SPEED);
- }
-
- @Override
- public double getDefaultArmor(@NotNull ItemStack itemStack) {
- return getAttributeValue(itemStack, Attribute.ARMOR);
- }
-
- @Override
- public double getDefaultToughness(@NotNull ItemStack itemStack) {
- return getAttributeValue(itemStack, Attribute.ARMOR_TOUGHNESS);
- }
-
- @Override
- public boolean isWeapon(@NotNull ItemStack itemStack) {
- net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(itemStack);
- Item item = nmsItem.getItem();
- return itemStack.getType().name().contains("SWORD") || item instanceof AxeItem || item instanceof TridentItem;
- }
-
- @Override
- public boolean isArmor(@NotNull ItemStack itemStack) {
- return itemStack.getItemMeta() instanceof ArmorMeta;
- }
-
- @Override
- public boolean isTool(@NotNull ItemStack itemStack) {
- Material material = itemStack.getType();
- String[] suffixes = {"_AXE", "_HOE", "_SHOVEL", "_PICKAXE", "_SHEARS"};
- return Arrays.stream(suffixes).anyMatch(suffix -> material.name().endsWith(suffix));
- }
-
- @Override
- public String toJson(@NotNull ItemStack item) {
- return "unsupported in 26.2";
- }
-
- @Override
- public void setKiller(@NotNull LivingEntity entity, @NotNull Player killer) {
- net.minecraft.world.entity.LivingEntity hit = ((CraftLivingEntity) entity).getHandle();
- hit.setLastHurtByPlayer(((CraftPlayer) killer).getHandle(), 100);
- }
-
- @Override
- public void changeSkull(@NotNull Block block, @NotNull String hash) {
- try {
- if (!(block.getState() instanceof Skull)) return;
-
- Skull skull = (Skull) block.getState();
- PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID(), hash.substring(0, 16));
- PlayerTextures textures = profile.getTextures();
-
- // If the hash is a URL, we can just set the skin directly, otherwise, we need to decode
- // the hash from Base64 and extract the url from the JSON data.
- if (hash.startsWith("http")) {
- textures.setSkin(new URL(hash));
- } else {
- String decoded;
- try {
- decoded = new String(Base64.getDecoder().decode(hash));
- } catch (IllegalArgumentException ignored) {
- try {
- decoded = new String(Base64.getMimeDecoder().decode(hash));
- } catch (IllegalArgumentException ignored2) {
- decoded = new String(Base64.getUrlDecoder().decode(hash));
- // If we throw again... we'll just break out
- }
- }
- // Construct the json object
- JsonObject json = new Gson().fromJson(decoded, JsonObject.class);
- // Get the textures object
- JsonObject texturesJson = json.getAsJsonObject("textures");
- // Get the skin object
- JsonObject skin = texturesJson.getAsJsonObject("SKIN");
- // Get the url
- String url = skin.get("url").getAsString();
- textures.setSkin(new URL(url));
- }
-
- profile = profile.update().get();
- skull.setOwnerProfile(profile);
- skull.update();
- } catch (Exception e) {
- Codex.warn("Could not update skull");
- e.printStackTrace();
- }
- }
-
- @Override
- public void addSkullTexture(@NotNull ItemStack item, @NotNull String value, @NotNull UUID uuid) {
- if (item.getType() != Material.PLAYER_HEAD) return;
-
- SkullMeta meta = (SkullMeta) item.getItemMeta();
- if (meta == null) return;
-
- try {
- PlayerProfile playerProfile = Bukkit.createPlayerProfile(uuid, uuid.toString().substring(0, 16));
- String decoded = new String(Base64.getDecoder().decode(value));
- JsonObject json = new Gson().fromJson(decoded, JsonObject.class);
- JsonObject texturesJson = json.getAsJsonObject("textures");
- JsonObject skin = texturesJson.getAsJsonObject("SKIN");
- String url = skin.get("url").getAsString();
- playerProfile.getTextures().setSkin(new URL(url));
- meta.setOwnerProfile(playerProfile);
- item.setItemMeta(meta);
- } catch (MalformedURLException | NoClassDefFoundError | NoSuchMethodError | IllegalArgumentException e) {
- NMS.super.addSkullTexture(item, value, uuid);
- }
- }
-
- @NotNull
- @Override
- public Attribute getAttribute(String name) {
- return Registry.ATTRIBUTE.getOrThrow(NamespacedKey.minecraft(name.toLowerCase(Locale.US)));
- }
-
- @Override
- public Object getNMSCopy(@NotNull ItemStack item) {
- return CraftItemStack.asNMSCopy(item);
- }
-
- @Override
- public Material getMaterial(Boat boat) {
- String boatClassName = boat.getClass().getSimpleName().replace("Craft", "");
- // Split on capital letters and join with underscores
- // ex: AcaciaBoat -> ACACIA_BOAT
- String materialName = String.join("_", boatClassName.split("(?=[A-Z])")).toUpperCase(Locale.US);
- Material mat = Material.matchMaterial(materialName);
-
- if (mat == null) mat = Material.OAK_BOAT;
-
- return mat;
- }
-
- @Override
- public Objective registerNewObjective(Scoreboard scoreboard, Objective objective) {
- return scoreboard.registerNewObjective(objective.getName(),
- objective.getTrackedCriteria(),
- objective.getDisplayName());
- }
-
- @Override
- @SuppressWarnings("deprecation")
- public HoverEvent getHoverEvent(@NotNull ItemStack itemStack) {
- String components = itemStack.getItemMeta() != null ? itemStack.getItemMeta().getAsString() : "{}";
- components = components.replaceAll(": ?0b", ": false")
- .replaceAll(": ?1b", ": true")
- .replaceAll(": ?(\\d+\\.\\d+)d", ": $1")
- .replaceAll("\\b(\\d+\\.\\d+)f\\b", "$1");
- return new HoverEvent(HoverEvent.Action.SHOW_ITEM,
- new ComponentsShowItem(
- itemStack.getType().getKey().toString(),
- itemStack.getAmount(),
- new Gson().fromJson(components, JsonObject.class))
- );
- }
-
- /**
- * Minimal show_item content carrying components for modern clients.
- */
- private static final class ComponentsShowItem extends Content {
- private final String id;
- private final int count;
- private final JsonElement components;
-
- private ComponentsShowItem(final String id, final int count, final JsonElement components) {
- this.id = id;
- this.count = count;
- this.components = components;
- }
-
- @Override
- public HoverEvent.Action requiredAction() {
- return HoverEvent.Action.SHOW_ITEM;
- }
- }
-
- @Override
- public BaseComponent getTranslatedComponent(@NotNull ItemStack itemStack) {
- ItemMeta meta = itemStack.getItemMeta();
- String string = null;
- if (meta != null) {
- string = meta.getDisplayName();
- if (string.isEmpty()) {
- try {
- string = meta.getItemName();
- } catch (NoSuchMethodError ignored) {
- }
- }
- }
-
- BaseComponent baseComponent;
- if (string == null || string.isEmpty()) {
- baseComponent = new TranslatableComponent(itemStack.getType().getItemTranslationKey());
- } else {
- baseComponent = new TextComponent(string);
- }
-
- return baseComponent;
- }
-
- @Override
- @SuppressWarnings("UnstableApiUsage")
- public EntityDamageByEntityEvent createEntityDamageEvent(@NotNull Entity entity,
- @NotNull Entity attacker,
- @NotNull EntityDamageByEntityEvent.DamageCause cause,
- double damage) {
- return new EntityDamageByEntityEvent(attacker,
- entity,
- cause,
- DamageSource.builder(DamageType.MOB_ATTACK).build(),
- damage);
- }
-}
diff --git a/codex-nms/pom.xml b/codex-nms/pom.xml
index d37a2865..436559cc 100644
--- a/codex-nms/pom.xml
+++ b/codex-nms/pom.xml
@@ -43,7 +43,6 @@
codex-nms-v1_21_7
codex-nms-v1_21_10
codex-nms-v1_21_11
- codex-nms-v26_1
codex-nms-v26_2