From 2408d540d85029541f411ab1c0471b0b6f3897b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 06:15:56 +0000 Subject: [PATCH 1/4] feat: add 26.3 NMS support Add codex-nms-v26_3 module built against Spigot 26.3, register it in codex-core, and map 26.3 servers to it in VersionManager and Version. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015SJWkKAESR9PJNPooW9ovC --- .../codex/compat/VersionManager.java | 1 + .../studio/magemonkey/codex/core/Version.java | 4 +- .../codex/compat/VersionManagerTest.java | 9 + codex-core/pom.xml | 5 + codex-nms/codex-nms-v26_3/pom.xml | 62 ++++ .../codex/nms/v26_3/ArmorUtilImpl.java | 72 ++++ .../codex/nms/v26_3/CompatImpl.java | 116 ++++++ .../magemonkey/codex/nms/v26_3/NMSImpl.java | 350 ++++++++++++++++++ codex-nms/pom.xml | 1 + 9 files changed, 619 insertions(+), 1 deletion(-) create mode 100644 codex-nms/codex-nms-v26_3/pom.xml create mode 100644 codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/ArmorUtilImpl.java create mode 100644 codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/CompatImpl.java create mode 100644 codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/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 e0650c24..0dd72e11 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 @@ -68,6 +68,7 @@ private static String getPackageFromVersion(String version) { 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.3" -> "v26_3"; 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..89af5572 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; @@ -57,6 +58,7 @@ else if (versionRaw.equals("craftbukkit")) { 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.3-R0.1-SNAPSHOT", "26.3" -> 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 index abb4ca81..5e39fec7 100644 --- a/codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java +++ b/codex-api/src/test/java/studio/magemonkey/codex/compat/VersionManagerTest.java @@ -14,4 +14,13 @@ void getPackageFromVersion_supportsPurpur2612() throws Exception { Object packageName = method.invoke(null, "26.1.2"); assert packageName.equals("v26_2"); } + + @Test + void getPackageFromVersion_supports263() throws Exception { + Method method = VersionManager.class.getDeclaredMethod("getPackageFromVersion", String.class); + method.setAccessible(true); + + Object packageName = method.invoke(null, "26.3"); + assert packageName.equals("v26_3"); + } } diff --git a/codex-core/pom.xml b/codex-core/pom.xml index 422bbccb..c5606d16 100644 --- a/codex-core/pom.xml +++ b/codex-core/pom.xml @@ -106,6 +106,11 @@ codex-nms-v26_2 ${nms.version} + + studio.magemonkey + codex-nms-v26_3 + ${nms.version} + diff --git a/codex-nms/codex-nms-v26_3/pom.xml b/codex-nms/codex-nms-v26_3/pom.xml new file mode 100644 index 00000000..4c384d09 --- /dev/null +++ b/codex-nms/codex-nms-v26_3/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + studio.magemonkey + codex-nms + 1.2.0-R0.1-SNAPSHOT + + + codex-nms-v26_3 + CodexNMS-26.3 + 26.3 NMS utilities for Codex + https://www.spigotmc.org/resources/93608/ + + + 21 + + + + + org.spigotmc + spigot-api + 26.3-R0.1-SNAPSHOT + provided + + + org.spigotmc + spigot + 26.3-R0.1-SNAPSHOT + provided + + + it.unimi.dsi + fastutil + 8.5.19 + 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 + + \ No newline at end of file diff --git a/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/ArmorUtilImpl.java b/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/ArmorUtilImpl.java new file mode 100644 index 00000000..4f685f89 --- /dev/null +++ b/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/ArmorUtilImpl.java @@ -0,0 +1,72 @@ +package studio.magemonkey.codex.nms.v26_3; + +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_3/src/main/java/studio/magemonkey/codex/nms/v26_3/CompatImpl.java b/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/CompatImpl.java new file mode 100644 index 00000000..a788d67c --- /dev/null +++ b/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/CompatImpl.java @@ -0,0 +1,116 @@ +package studio.magemonkey.codex.nms.v26_3; + +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.EquipmentSlot; +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 org.jetbrains.annotations.Nullable; +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, + @Nullable EquipmentSlot slot) { + return new AttributeModifier(attribute.getAttribute().getKey(), amount, operation, toSlotGroup(slot)); + } + + private static EquipmentSlotGroup toSlotGroup(@Nullable EquipmentSlot slot) { + if (slot == null) return EquipmentSlotGroup.ANY; + return switch (slot) { + case HAND -> EquipmentSlotGroup.MAINHAND; + case OFF_HAND -> EquipmentSlotGroup.OFFHAND; + case FEET -> EquipmentSlotGroup.FEET; + case LEGS -> EquipmentSlotGroup.LEGS; + case CHEST -> EquipmentSlotGroup.CHEST; + case HEAD -> EquipmentSlotGroup.HEAD; + default -> 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_3/src/main/java/studio/magemonkey/codex/nms/v26_3/NMSImpl.java b/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/NMSImpl.java new file mode 100644 index 00000000..884a7b9b --- /dev/null +++ b/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/NMSImpl.java @@ -0,0 +1,350 @@ +package studio.magemonkey.codex.nms.v26_3; + +import com.google.common.base.Preconditions; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.serialization.JsonOps; +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.resources.RegistryOps; +import net.minecraft.server.MinecraftServer; +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.3"; + } + + @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.3"; + } + + @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) { + net.minecraft.world.item.ItemStack nmsItem = CraftItemStack.asNMSCopy(itemStack); + // Encoding straight from the component map avoids the SNBT-string detour (getAsString()) entirely, + // so there's no numeric-suffix text to mis-parse. + JsonElement encoded = net.minecraft.world.item.ItemStack.CODEC + .encodeStart(RegistryOps.create(JsonOps.INSTANCE, MinecraftServer.getServer().registryAccess()), nmsItem) + .getOrThrow(); + JsonObject encodedObj = encoded.getAsJsonObject(); + JsonObject componentsObj = encodedObj.has("components") + ? encodedObj.getAsJsonObject("components") + : new JsonObject(); + return new HoverEvent(HoverEvent.Action.SHOW_ITEM, + new ComponentsShowItem( + itemStack.getType().getKey().toString(), + itemStack.getAmount(), + componentsObj) + ); + } + + /** + * 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 cddee4b2..f6d8dbbb 100644 --- a/codex-nms/pom.xml +++ b/codex-nms/pom.xml @@ -44,6 +44,7 @@ codex-nms-v1_21_10 codex-nms-v1_21_11 codex-nms-v26_2 + codex-nms-v26_3 From 089d8338e255b581772e8178744c334b9fd24659 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 06:17:33 +0000 Subject: [PATCH 2/4] fix: replace removed AxeItem check in 26.3 NMS AxeItem no longer exists in 26.3, so identify axes via Tag.ITEMS_AXES. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015SJWkKAESR9PJNPooW9ovC --- .../java/studio/magemonkey/codex/nms/v26_3/NMSImpl.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/NMSImpl.java b/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/NMSImpl.java index 884a7b9b..fcf5fa59 100644 --- a/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/NMSImpl.java +++ b/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/NMSImpl.java @@ -21,13 +21,13 @@ 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.Tag; import org.bukkit.attribute.Attribute; import org.bukkit.block.Block; import org.bukkit.block.Skull; @@ -146,7 +146,10 @@ public double getDefaultToughness(@NotNull ItemStack itemStack) { 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; + // AxeItem was removed in 26.3; axes are plain items now, so identify them by item tag instead + return itemStack.getType().name().contains("SWORD") + || Tag.ITEMS_AXES.isTagged(itemStack.getType()) + || item instanceof TridentItem; } @Override From 9fad04eef18f72a417f31b5c74d5c5717c86ddc9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 20:49:12 +0000 Subject: [PATCH 3/4] fix: point codex-nms at the current codex-parent codex-nms pinned codex-parent/codex-api 1.2.0-R0.4-SNAPSHOT, whose dependency on dev.lone:api-itemsadder:4.0.10 no longer resolves from any configured repository. Bump to 1.2.0-R0.7-SNAPSHOT and resolve the parent from the reactor via relativePath. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015SJWkKAESR9PJNPooW9ovC --- codex-nms/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codex-nms/pom.xml b/codex-nms/pom.xml index f6d8dbbb..d4d667c7 100644 --- a/codex-nms/pom.xml +++ b/codex-nms/pom.xml @@ -6,8 +6,8 @@ studio.magemonkey codex-parent - 1.2.0-R0.4-SNAPSHOT - + 1.2.0-R0.7-SNAPSHOT + ../pom.xml @@ -51,7 +51,7 @@ studio.magemonkey codex-api - 1.2.0-R0.4-SNAPSHOT + 1.2.0-R0.7-SNAPSHOT provided From 17880fd0e4d83d4217a222df7069e61a449b24ff Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 21:49:59 +0000 Subject: [PATCH 4/4] fix: pin codex-nms to the published 1.2.0-R0.6 codex-parent/api NMS modules are deployed independently, so codex-nms must reference published artifacts rather than the unreleased dev version or the in-repo parent. R0.6 is the latest published snapshot and no longer pulls the unresolvable dev.lone:api-itemsadder:4.0.10. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015SJWkKAESR9PJNPooW9ovC --- codex-nms/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codex-nms/pom.xml b/codex-nms/pom.xml index d4d667c7..70c6e6a2 100644 --- a/codex-nms/pom.xml +++ b/codex-nms/pom.xml @@ -6,8 +6,8 @@ studio.magemonkey codex-parent - 1.2.0-R0.7-SNAPSHOT - ../pom.xml + 1.2.0-R0.6-SNAPSHOT + @@ -51,7 +51,7 @@ studio.magemonkey codex-api - 1.2.0-R0.7-SNAPSHOT + 1.2.0-R0.6-SNAPSHOT provided