1212import net .minecraft .nbt .NbtOps ;
1313import net .minecraft .util .datafix .DataFixers ;
1414import net .minecraft .util .datafix .fixes .References ;
15+ import net .kyori .adventure .text .Component ;
16+ import net .kyori .adventure .text .serializer .gson .GsonComponentSerializer ;
17+ import net .kyori .adventure .text .serializer .plain .PlainTextComponentSerializer ;
1518import org .bukkit .Bukkit ;
19+ import org .bukkit .ChatColor ;
1620import org .bukkit .UnsafeValues ;
1721import org .bukkit .World ;
1822import org .bukkit .craftbukkit .CraftWorld ;
1923import org .bukkit .craftbukkit .inventory .CraftItemStack ;
2024import org .bukkit .inventory .ItemStack ;
25+ import org .bukkit .inventory .meta .ItemMeta ;
2126
2227import java .io .*;
2328import java .util .ArrayList ;
2429import java .util .Collections ;
2530import java .util .List ;
2631import java .util .Optional ;
32+ import java .util .Objects ;
2733import java .util .stream .Collectors ;
2834import java .util .zip .Deflater ;
2935import java .util .zip .DeflaterInputStream ;
@@ -91,18 +97,12 @@ public static ItemStack itemFromBinary(byte[] nbt) throws IOException {
9197 return null ;
9298 }
9399
94- // Try modern format first (GZIP compressed)
95- if (nbt .length >= 2 && nbt [0 ] == (byte ) 0x1f && nbt [1 ] == (byte ) 0x8b ) {
96- try {
97- return ItemStack .deserializeBytes (nbt );
98- } catch (Exception e ) {
99- // If it fails, try to apply DataFixer for legacy data
100- return deserializeWithDataFixer (nbt , true );
101- }
100+ boolean isGzipped = isGzipCompressed (nbt );
101+ try {
102+ return deserializeWithDataFixer (nbt , isGzipped );
103+ } catch (Exception ex ) {
104+ return ItemStack .deserializeBytes (nbt );
102105 }
103-
104- // Legacy format (not GZIP) - apply DataFixer
105- return deserializeWithDataFixer (nbt , false );
106106 }
107107
108108 /**
@@ -145,6 +145,8 @@ private static ItemStack deserializeWithDataFixer(byte[] nbt, boolean isGzipped)
145145 dynamic = dataFixer .update (References .ITEM_STACK , dynamic , dataVersion , currentDataVersion );
146146 tag = (CompoundTag ) dynamic .getValue ();
147147 tag .putInt ("DataVersion" , currentDataVersion );
148+ } else if (isGzipped ) {
149+ return ItemStack .deserializeBytes (nbt );
148150 }
149151
150152 // Serialize back to bytes with GZIP and deserialize using Paper's method
@@ -157,6 +159,10 @@ private static ItemStack deserializeWithDataFixer(byte[] nbt, boolean isGzipped)
157159 }
158160 }
159161
162+ private static boolean isGzipCompressed (byte [] nbt ) {
163+ return nbt .length >= 2 && nbt [0 ] == (byte ) 0x1f && nbt [1 ] == (byte ) 0x8b ;
164+ }
165+
160166 @ Deprecated
161167 public static ItemStack itemFromBinary (byte [] nbt , int offset , int len ) {
162168 return ItemStack .deserializeBytes (nbt );
@@ -285,4 +291,64 @@ public static String itemToJson(ItemStack itemStack) throws RuntimeException {
285291 public static Object asNMSCopy (ItemStack itemStack ) {
286292 return CraftItemStack .asNMSCopy (itemStack );
287293 }
294+
295+ public static boolean isSimilarPlainText (ItemStack base , ItemStack given ) {
296+ if (base == null || given == null ) return false ;
297+ if (!Objects .equals (base .getType (), given .getType ())) return false ;
298+ String baseName = getPlainDisplayName (base );
299+ String givenName = getPlainDisplayName (given );
300+ if (!Objects .equals (baseName , givenName )) return false ;
301+ List <String > baseLore = getPlainLore (base );
302+ List <String > givenLore = getPlainLore (given );
303+ return Objects .equals (baseLore , givenLore );
304+ }
305+
306+ public static String getPlainDisplayName (ItemStack item ) {
307+ if (item == null ) return "" ;
308+ ItemMeta meta = item .getItemMeta ();
309+ if (meta == null ) return "" ;
310+ if (meta .hasDisplayName ()) {
311+ Component name = meta .displayName ();
312+ if (name != null ) {
313+ return PlainTextComponentSerializer .plainText ().serialize (name );
314+ }
315+ return plainTextFromString (meta .getDisplayName ());
316+ }
317+ return "" ;
318+ }
319+
320+ public static List <String > getPlainLore (ItemStack item ) {
321+ if (item == null ) return Collections .emptyList ();
322+ ItemMeta meta = item .getItemMeta ();
323+ if (meta == null || !meta .hasLore ()) return Collections .emptyList ();
324+ List <Component > componentLore = meta .lore ();
325+ if (componentLore != null ) {
326+ List <String > plain = new ArrayList <>(componentLore .size ());
327+ for (Component line : componentLore ) {
328+ plain .add (PlainTextComponentSerializer .plainText ().serialize (line ));
329+ }
330+ return plain ;
331+ }
332+ List <String > legacyLore = meta .getLore ();
333+ if (legacyLore == null ) return Collections .emptyList ();
334+ List <String > plain = new ArrayList <>(legacyLore .size ());
335+ for (String line : legacyLore ) {
336+ plain .add (plainTextFromString (line ));
337+ }
338+ return plain ;
339+ }
340+
341+ private static String plainTextFromString (String text ) {
342+ if (text == null || text .isEmpty ()) return "" ;
343+ String trimmed = text .trim ();
344+ if ((trimmed .startsWith ("{" ) && trimmed .endsWith ("}" )) || (trimmed .startsWith ("[" ) && trimmed .endsWith ("]" ))) {
345+ try {
346+ Component component = GsonComponentSerializer .gson ().deserialize (trimmed );
347+ return PlainTextComponentSerializer .plainText ().serialize (component );
348+ } catch (Exception ignored ) {
349+ // Fall back to legacy handling.
350+ }
351+ }
352+ return ChatColor .stripColor (text );
353+ }
288354}
0 commit comments