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..fcf5fa59
--- /dev/null
+++ b/codex-nms/codex-nms-v26_3/src/main/java/studio/magemonkey/codex/nms/v26_3/NMSImpl.java
@@ -0,0 +1,353 @@
+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.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;
+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();
+ // 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
+ 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..70c6e6a2 100644
--- a/codex-nms/pom.xml
+++ b/codex-nms/pom.xml
@@ -6,7 +6,7 @@
studio.magemonkey
codex-parent
- 1.2.0-R0.4-SNAPSHOT
+ 1.2.0-R0.6-SNAPSHOT
@@ -44,13 +44,14 @@
codex-nms-v1_21_10
codex-nms-v1_21_11
codex-nms-v26_2
+ codex-nms-v26_3
studio.magemonkey
codex-api
- 1.2.0-R0.4-SNAPSHOT
+ 1.2.0-R0.6-SNAPSHOT
provided