001package net.tnemc.item.paper.platform;
002/*
003 * The New Item Library
004 * Copyright (C) 2022 - 2025 Daniel "creatorfromhell" Vidmar
005 *
006 * This program is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020
021import io.papermc.paper.registry.RegistryAccess;
022import io.papermc.paper.registry.RegistryKey;
023import net.tnemc.item.AbstractItemStack;
024import net.tnemc.item.bukkitbase.platform.providers.ItemAdderProvider;
025import net.tnemc.item.bukkitbase.platform.providers.MMOItemProvider;
026import net.tnemc.item.bukkitbase.platform.providers.NexoProvider;
027import net.tnemc.item.bukkitbase.platform.providers.NovaProvider;
028import net.tnemc.item.bukkitbase.platform.providers.OraxenProvider;
029import net.tnemc.item.bukkitbase.platform.providers.SlimefunProvider;
030import net.tnemc.item.paper.PaperCalculationsProvider;
031import net.tnemc.item.paper.PaperItemStack;
032import net.tnemc.item.paper.VanillaProvider;
033import net.tnemc.item.paper.platform.impl.modern.PaperBundleComponent;
034import net.tnemc.item.paper.platform.impl.modern.PaperContainerComponent;
035import net.tnemc.item.paper.platform.impl.modern.PaperCustomNameComponent;
036import net.tnemc.item.paper.platform.impl.modern.PaperDamageComponent;
037import net.tnemc.item.paper.platform.impl.modern.PaperEnchantmentsComponent;
038import net.tnemc.item.paper.platform.impl.modern.PaperItemModelComponent;
039import net.tnemc.item.paper.platform.impl.modern.PaperItemNameComponent;
040import net.tnemc.item.paper.platform.impl.modern.PaperLoreComponent;
041import net.tnemc.item.paper.platform.impl.modern.PaperModelDataComponent;
042import net.tnemc.item.paper.platform.impl.modern.PaperProfileComponent;
043import net.tnemc.item.paper.platform.impl.modern.PaperShulkerColorComponent;
044import net.tnemc.item.paper.platform.impl.old.PaperOldBundleComponent;
045import net.tnemc.item.paper.platform.impl.old.PaperOldContainerComponent;
046import net.tnemc.item.paper.platform.impl.old.PaperOldCustomNameComponent;
047import net.tnemc.item.paper.platform.impl.old.PaperOldDamageComponent;
048import net.tnemc.item.paper.platform.impl.old.PaperOldEnchantmentsComponent;
049import net.tnemc.item.paper.platform.impl.old.PaperOldItemModelComponent;
050import net.tnemc.item.paper.platform.impl.old.PaperOldItemNameComponent;
051import net.tnemc.item.paper.platform.impl.old.PaperOldLoreComponent;
052import net.tnemc.item.paper.platform.impl.old.PaperOldMaxStackSizeComponent;
053import net.tnemc.item.paper.platform.impl.old.PaperOldModelDataComponent;
054import net.tnemc.item.paper.platform.impl.old.PaperOldModelDataLegacyComponent;
055import net.tnemc.item.paper.platform.impl.old.PaperOldProfileComponent;
056import net.tnemc.item.paper.platform.impl.old.PaperOldShulkerColorComponent;
057import net.tnemc.item.platform.ItemPlatform;
058import net.tnemc.item.providers.CalculationsProvider;
059import net.tnemc.item.providers.ItemProvider;
060import net.tnemc.item.providers.VersionUtil;
061import org.bukkit.Bukkit;
062import org.bukkit.DyeColor;
063import org.bukkit.NamespacedKey;
064import org.bukkit.Registry;
065import org.bukkit.attribute.AttributeModifier;
066import org.bukkit.block.banner.PatternType;
067import org.bukkit.enchantments.Enchantment;
068import org.bukkit.inventory.*;
069import org.bukkit.inventory.meta.trim.TrimMaterial;
070import org.bukkit.inventory.meta.trim.TrimPattern;
071import org.bukkit.potion.PotionEffectType;
072import org.jetbrains.annotations.NotNull;
073import org.json.simple.JSONObject;
074import org.json.simple.parser.ParseException;
075
076import java.util.Locale;
077import java.util.Optional;
078
079/**
080 * PaperItemPlatform
081 *
082 * @author creatorfromhell
083 * @since 0.1.7.7
084 */
085public class PaperItemPlatform extends ItemPlatform<PaperItemStack, ItemStack, Inventory> {
086
087  private static volatile PaperItemPlatform instance;
088
089  //public static final PaperItemPlatform PLATFORM = new PaperItemPlatform();
090
091  protected final VanillaProvider defaultProvider = new VanillaProvider();
092  protected final PaperCalculationsProvider calculationsProvider = new PaperCalculationsProvider();
093
094  private PaperItemPlatform() {
095
096    super();
097  }
098
099  @Override
100  public PaperItemStack createStack(final String material) {
101    return new PaperItemStack().of(material, 1);
102  }
103
104  public static PaperItemPlatform instance() {
105
106    final PaperItemPlatform result = instance;
107    if(result != null) {
108      return result;
109    }
110
111    synchronized(PaperItemPlatform.class) {
112
113      if(instance == null) {
114
115        instance = new PaperItemPlatform();
116        instance.addDefaults();
117      }
118      return instance;
119    }
120  }
121
122  /**
123   * @return the version that is being used currently
124   * @since 0.2.0.0
125   */
126  @Override
127  public String version() {
128    return Bukkit.getServer().getBukkitVersion().split("-")[0];
129  }
130
131  @Override
132  public void addDefaults() {
133
134    registerConversions();
135
136    //bukkit base implementation.
137    if(VersionUtil.isLessThan(version(), "1.21.4")) {
138      addMulti(new PaperOldBundleComponent());
139      addMulti(new PaperOldContainerComponent());
140      addMulti(new PaperOldCustomNameComponent());
141      addMulti(new PaperOldDamageComponent());
142      addMulti(new PaperOldEnchantmentsComponent());
143      addMulti(new PaperOldItemModelComponent());
144      addMulti(new PaperOldItemNameComponent());
145      addMulti(new PaperOldLoreComponent());
146      addMulti(new PaperOldMaxStackSizeComponent());
147      addMulti(new PaperOldModelDataComponent());
148      addMulti(new PaperOldModelDataLegacyComponent());
149      addMulti(new PaperOldProfileComponent());
150      addMulti(new PaperOldShulkerColorComponent());
151    }
152
153    //Paper-specific
154    if(VersionUtil.isOneTwentyOneFour(version())) {
155      addMulti(new PaperBundleComponent());
156      addMulti(new PaperContainerComponent());
157      addMulti(new PaperCustomNameComponent());
158      addMulti(new PaperDamageComponent());
159      addMulti(new PaperEnchantmentsComponent());
160      addMulti(new PaperItemModelComponent());
161      addMulti(new PaperItemNameComponent());
162      addMulti(new PaperLoreComponent());
163      addMulti(new PaperModelDataComponent());
164      addMulti(new PaperOldModelDataLegacyComponent());
165      addMulti(new PaperProfileComponent());
166      addMulti(new PaperShulkerColorComponent());
167    }
168
169
170    if(Bukkit.getPluginManager().isPluginEnabled("ItemsAdder")) {
171      addItemProvider(new ItemAdderProvider());
172    }
173
174    if(Bukkit.getPluginManager().isPluginEnabled("MythicMobs")) {
175      addItemProvider(new MMOItemProvider());
176    }
177
178    if(Bukkit.getPluginManager().isPluginEnabled("Nexo")) {
179      addItemProvider(new NexoProvider());
180    }
181
182    if(Bukkit.getPluginManager().isPluginEnabled("Nova")) {
183      addItemProvider(new NovaProvider());
184    }
185
186    if(Bukkit.getPluginManager().isPluginEnabled("Oraxen")) {
187      addItemProvider(new OraxenProvider());
188    }
189
190    if(Bukkit.getPluginManager().isPluginEnabled("Slimefun")) {
191      addItemProvider(new SlimefunProvider());
192    }
193    addItemProvider(defaultProvider);
194  }
195
196  /**
197   * Retrieves the default provider for the item stack comparison.
198   *
199   * @return the default provider for the item stack comparison.
200   * @since 0.2.0.0
201   */
202  @Override
203  public @NotNull ItemProvider<ItemStack> defaultProvider() {
204
205    return defaultProvider;
206  }
207
208  /**
209   * Retrieves the identifier of the default provider for the item stack comparison.
210   *
211   * @return The identifier of the default provider for the item stack comparison.
212   * @since 0.2.0.0
213   */
214  @Override
215  public @NotNull String defaultProviderIdentifier() {
216
217    return defaultProvider.identifier();
218  }
219
220  @Override
221  public PaperCalculationsProvider calculations() {
222    return calculationsProvider;
223  }
224
225  /**
226   * Converts the given locale stack to an instance of {@link AbstractItemStack}
227   *
228   * @param locale the locale to convert
229   *
230   * @return the converted locale of type I
231   * @since 0.2.0.0
232   */
233  @Override
234  public PaperItemStack locale(final ItemStack locale) {
235
236    return new PaperItemStack().of(locale);
237  }
238
239  private void registerConversions() {
240
241    //RegisterConversion for EquipmentSlot
242    converter.registerConversion(String.class, EquipmentSlot.class, input -> {
243      switch (input.toUpperCase()) {
244        case "HAND": return EquipmentSlot.HAND;
245        case "OFF_HAND": return EquipmentSlot.OFF_HAND;
246        case "FEET": return EquipmentSlot.FEET;
247        case "LEGS": return EquipmentSlot.LEGS;
248        case "CHEST": return EquipmentSlot.CHEST;
249        case "HEAD": return EquipmentSlot.HEAD;
250        case "BODY": return EquipmentSlot.BODY;
251        default: return EquipmentSlot.HAND;
252      }
253    });
254
255    converter.registerConversion(EquipmentSlot.class, String.class, input->switch(input) {
256      case HAND -> "HAND";
257      case OFF_HAND -> "OFF_HAND";
258      case FEET -> "FEET";
259      case LEGS -> "LEGS";
260      case CHEST -> "CHEST";
261      case HEAD -> "HEAD";
262      case BODY -> "BODY";
263    });
264
265    //RegisterConversion for EquipmentSlotGroup
266    converter.registerConversion(String.class, EquipmentSlotGroup.class, input -> {
267      final EquipmentSlotGroup group = EquipmentSlotGroup.getByName(input);
268      if(group == null) {
269        throw new IllegalArgumentException("Unknown input: " + input);
270      }
271      return group;
272    });
273
274    converter.registerConversion(EquipmentSlotGroup.class, String.class, EquipmentSlotGroup::toString);
275
276    //Register Conversions for AttributeModifier
277    converter.registerConversion(String.class, AttributeModifier.Operation.class, input->switch(input.toLowerCase()) {
278      case "add_value" -> AttributeModifier.Operation.ADD_NUMBER;
279      case "add_multiplied_base" -> AttributeModifier.Operation.ADD_SCALAR;
280      case "add_multiplied_total" -> AttributeModifier.Operation.MULTIPLY_SCALAR_1;
281      default -> throw new IllegalArgumentException("Unknown input: " + input);
282    });
283
284    converter.registerConversion(AttributeModifier.Operation.class, String.class, input ->switch(input) {
285      case ADD_NUMBER -> "add_value";
286      case ADD_SCALAR -> "add_multiplied_base";
287      case MULTIPLY_SCALAR_1 -> "add_multiplied_total";
288    });
289
290    //Register conversions for DyeColor
291    converter.registerConversion(String.class, DyeColor.class, input ->switch(input.toLowerCase(Locale.ROOT)) {
292      case "white" -> DyeColor.WHITE;
293      case "orange" -> DyeColor.ORANGE;
294      case "magenta" -> DyeColor.MAGENTA;
295      case "light_blue" -> DyeColor.LIGHT_BLUE;
296      case "yellow" -> DyeColor.YELLOW;
297      case "lime" -> DyeColor.LIME;
298      case "pink" -> DyeColor.PINK;
299      case "gray" -> DyeColor.GRAY;
300      case "light_gray" -> DyeColor.LIGHT_GRAY;
301      case "cyan" -> DyeColor.CYAN;
302      case "purple" -> DyeColor.PURPLE;
303      case "blue" -> DyeColor.BLUE;
304      case "brown" -> DyeColor.BROWN;
305      case "green" -> DyeColor.GREEN;
306      case "red" -> DyeColor.RED;
307      case "black" -> DyeColor.BLACK;
308      default -> throw new IllegalArgumentException("Unknown DyeColor: " + input);
309    });
310
311    converter.registerConversion(DyeColor.class, String.class, input ->switch(input) {
312      case WHITE -> "white";
313      case ORANGE -> "orange";
314      case MAGENTA -> "magenta";
315      case LIGHT_BLUE -> "light_blue";
316      case YELLOW -> "yellow";
317      case LIME -> "lime";
318      case PINK -> "pink";
319      case GRAY -> "gray";
320      case LIGHT_GRAY -> "light_gray";
321      case CYAN -> "cyan";
322      case PURPLE -> "purple";
323      case BLUE -> "blue";
324      case BROWN -> "brown";
325      case GREEN -> "green";
326      case RED -> "red";
327      case BLACK -> "black";
328    });
329
330    //Register Conversions for PatternType, which will be dependent on versions
331    //We'll separate the legacy find methods from the modern ones in order to maintain one component
332    // class for both.
333    if(VersionUtil.isOneTwentyOne(version())) {
334
335      converter.registerConversion(String.class, PatternType.class, input->{
336
337        final NamespacedKey key = NamespacedKey.fromString(input);
338        if(key != null) {
339
340          final PatternType patternType = Registry.BANNER_PATTERN.get(key);
341          if(patternType != null) {
342
343            return patternType;
344          }
345        }
346        throw new IllegalArgumentException("Unknown PatternType: " + input);
347      });
348
349      if(VersionUtil.isOneTwentyOneFour(version())) {
350
351        converter.registerConversion(PatternType.class, String.class, input->{
352
353          final NamespacedKey key = input.getKey();
354
355          return key.toString();
356        });
357      } else {
358
359        converter.registerConversion(PatternType.class, String.class, input->{
360
361          final NamespacedKey key = input.getKey();
362
363          return key.toString();
364        });
365      }
366
367    } else {
368
369      converter.registerConversion(String.class, PatternType.class, PatternType::valueOf);
370
371      converter.registerConversion(PatternType.class, String.class, PatternType::getIdentifier);
372    }
373
374    //Register Conversions for Enchantment, which will be dependent on versions
375    //We'll separate the legacy find methods from the modern ones in order to maintain one component
376    // class for both.
377    if(VersionUtil.isOneTwentyOneFour(version())) {
378
379      converter.registerConversion(String.class, Enchantment.class, (final String input)->{
380
381        final NamespacedKey key = NamespacedKey.fromString(input);
382        if(key != null) {
383
384          return Registry.ENCHANTMENT.getOrThrow(key);
385        }
386        throw new IllegalArgumentException("Unknown Enchantment: " + input);
387      });
388
389      converter.registerConversion(Enchantment.class, String.class, (final Enchantment input)->input.getKey().toString());
390
391    } else if(VersionUtil.isOneThirteen(version())) {
392      converter.registerConversion(String.class, Enchantment.class, (final String input)->{
393
394        final Enchantment enchantment = Enchantment.getByKey(NamespacedKey.fromString(input));
395        if(enchantment == null) {
396
397          throw new IllegalArgumentException("Unknown Enchantment: " + input);
398        }
399        return enchantment;
400      });
401
402      converter.registerConversion(Enchantment.class, String.class, (final Enchantment input)->input.getKey().getKey());
403    } else {
404
405      converter.registerConversion(String.class, Enchantment.class, (final String input)->{
406
407        final Enchantment enchantment = Enchantment.getByName(input);
408        if(enchantment == null) {
409
410          throw new IllegalArgumentException("Unknown Enchantment: " + input);
411        }
412        return enchantment;
413      });
414
415      converter.registerConversion(Enchantment.class, String.class, Enchantment::getName);
416    }
417
418    //Register Conversions for Trim Material, which will be dependent on versions
419    //We'll separate the legacy find methods from the modern ones in order to maintain one component
420    // class for both.
421    if(VersionUtil.isOneTwentyOneFour(version())) {
422
423      converter.registerConversion(TrimMaterial.class, String.class, input->{
424
425        final NamespacedKey key = RegistryAccess.registryAccess().getRegistry(RegistryKey.TRIM_MATERIAL).getKey(input);
426        if(key != null) {
427
428          return key.asString();
429        }
430
431        throw new IllegalArgumentException("Unknown TrimMaterial: " + input);
432      });
433
434      converter.registerConversion(String.class, TrimMaterial.class, input->{
435        final NamespacedKey key = NamespacedKey.fromString(input);
436        if(key != null) {
437
438          return RegistryAccess.registryAccess().getRegistry(RegistryKey.TRIM_MATERIAL).getOrThrow(key);
439        }
440        throw new IllegalArgumentException("Unknown TrimMaterial: " + input);
441      });
442
443    } else if(VersionUtil.isOneTwenty(version())) {
444
445      converter.registerConversion(TrimMaterial.class, String.class, input->input.getKey().toString());
446
447      converter.registerConversion(String.class, TrimMaterial.class, input->{
448        final NamespacedKey key = NamespacedKey.fromString(input);
449        if(key != null) {
450
451          return Registry.TRIM_MATERIAL.get(key);
452        }
453        throw new IllegalArgumentException("Unknown TrimMaterial: " + input);
454      });
455    }
456
457    //Register Conversions for NamespacedKey, which will be dependent on versions
458    //We'll separate the legacy find methods from the modern ones in order to maintain one component
459    // class for both.
460    converter.registerConversion(ItemRarity.class, String.class, input->switch(input) {
461      case EPIC -> "epic";
462      case RARE -> "rare";
463      case UNCOMMON -> "uncommon";
464      default -> "common";
465    });
466
467    converter.registerConversion(String.class, ItemRarity.class, input->switch(input.toLowerCase()) {
468      case "epic" -> ItemRarity.EPIC;
469      case "rare" -> ItemRarity.RARE;
470      case "uncommon" -> ItemRarity.UNCOMMON;
471      default -> ItemRarity.COMMON;
472    });
473
474    //Register Conversions for Trim Pattern, which will be dependent on versions
475    //We'll separate the legacy find methods from the modern ones in order to maintain one component
476    // class for both.
477    if(VersionUtil.isOneTwentyOneFour(version())) {
478
479      converter.registerConversion(TrimPattern.class, String.class, input->{
480
481        final NamespacedKey key = RegistryAccess.registryAccess().getRegistry(RegistryKey.TRIM_PATTERN).getKey(input);
482        if(key != null) {
483
484          return key.asString();
485        }
486
487        throw new IllegalArgumentException("Unknown TrimPattern: " + input);
488      });
489
490      converter.registerConversion(String.class, TrimPattern.class, input->{
491        final NamespacedKey key = NamespacedKey.fromString(input);
492        if(key != null) {
493
494          return RegistryAccess.registryAccess().getRegistry(RegistryKey.TRIM_PATTERN).getOrThrow(key);
495        }
496        throw new IllegalArgumentException("Unknown TrimPattern: " + input);
497      });
498
499    } else if(VersionUtil.isOneTwenty(version())) {
500
501      converter.registerConversion(TrimPattern.class, String.class, input->input.getKey().toString());
502
503      converter.registerConversion(String.class, TrimPattern.class, input->{
504        final NamespacedKey key = NamespacedKey.fromString(input);
505        if(key != null) {
506
507          return Registry.TRIM_PATTERN.get(key);
508        }
509        throw new IllegalArgumentException("Unknown TrimPattern: " + input);
510      });
511    }
512
513    if(VersionUtil.isOneTwentyOneFour(version())) {
514
515      converter.registerConversion(PotionEffectType.class, String.class, input->input.getKey().toString());
516      converter.registerConversion(String.class, PotionEffectType.class, input->{
517        final NamespacedKey key = NamespacedKey.fromString(input);
518        if(key != null) {
519
520          return Registry.EFFECT.getOrThrow(key);
521        }
522        throw new IllegalArgumentException("Unknown PotionEffectType: " + input);
523      });
524    } else {
525
526      converter.registerConversion(PotionEffectType.class, String.class, PotionEffectType::getName);
527      converter.registerConversion(String.class, PotionEffectType.class, input->{
528
529        final PotionEffectType type = PotionEffectType.getByName(input);
530        if(type != null) {
531
532          return type;
533        }
534        throw new IllegalArgumentException("Unknown PotionEffectType: " + input);
535      });
536    }
537  }
538
539  /**
540   * Initializes and returns an AbstractItemStack object by deserializing the provided JSON object.
541   *
542   * @param object the JSON object to deserialize
543   *
544   * @return an initialized AbstractItemStack object
545   * @since 0.2.0.0
546   */
547  @Override
548  public Optional<PaperItemStack> initSerialized(final JSONObject object) {
549
550    try {
551      return Optional.ofNullable(new PaperItemStack().of(object));
552    } catch(final ParseException e) {
553      return Optional.empty();
554    }
555  }
556}