001package net.tnemc.item.bukkit.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 net.tnemc.item.AbstractItemStack;
022import net.tnemc.item.bukkit.BukkitCalculationsProvider;
023import net.tnemc.item.bukkit.BukkitItemStack;
024import net.tnemc.item.bukkit.VanillaProvider;
025import net.tnemc.item.bukkit.platform.impl.BukkitBundleComponent;
026import net.tnemc.item.bukkit.platform.impl.BukkitContainerComponent;
027import net.tnemc.item.bukkit.platform.impl.BukkitCustomNameComponent;
028import net.tnemc.item.bukkit.platform.impl.BukkitDamageComponent;
029import net.tnemc.item.bukkit.platform.impl.BukkitEnchantmentsComponent;
030import net.tnemc.item.bukkit.platform.impl.BukkitItemModelComponent;
031import net.tnemc.item.bukkit.platform.impl.BukkitItemNameComponent;
032import net.tnemc.item.bukkit.platform.impl.BukkitLoreComponent;
033import net.tnemc.item.bukkit.platform.impl.BukkitMaxStackSizeComponent;
034import net.tnemc.item.bukkit.platform.impl.BukkitModelDataComponent;
035import net.tnemc.item.bukkit.platform.impl.BukkitModelDataOldComponent;
036import net.tnemc.item.bukkit.platform.impl.BukkitProfileComponent;
037import net.tnemc.item.bukkit.platform.impl.BukkitShulkerColorComponent;
038import net.tnemc.item.bukkitbase.platform.providers.ItemAdderProvider;
039import net.tnemc.item.bukkitbase.platform.providers.MMOItemProvider;
040import net.tnemc.item.bukkitbase.platform.providers.NexoProvider;
041import net.tnemc.item.bukkitbase.platform.providers.NovaProvider;
042import net.tnemc.item.bukkitbase.platform.providers.OraxenProvider;
043import net.tnemc.item.bukkitbase.platform.providers.SlimefunProvider;
044import net.tnemc.item.platform.ItemPlatform;
045import net.tnemc.item.providers.CalculationsProvider;
046import net.tnemc.item.providers.ItemProvider;
047import net.tnemc.item.providers.VersionUtil;
048import org.bukkit.Bukkit;
049import org.bukkit.DyeColor;
050import org.bukkit.NamespacedKey;
051import org.bukkit.Registry;
052import org.bukkit.attribute.AttributeModifier;
053import org.bukkit.block.banner.PatternType;
054import org.bukkit.enchantments.Enchantment;
055import org.bukkit.inventory.*;
056import org.bukkit.inventory.meta.trim.TrimMaterial;
057import org.bukkit.inventory.meta.trim.TrimPattern;
058import org.bukkit.potion.PotionEffectType;
059import org.jetbrains.annotations.NotNull;
060import org.json.simple.JSONObject;
061import org.json.simple.parser.ParseException;
062
063import java.util.Locale;
064import java.util.Optional;
065
066/**
067 * BukkitItemPlatform
068 *
069 * @author creatorfromhell
070 * @since 0.1.7.7
071 */
072public class BukkitItemPlatform extends ItemPlatform<BukkitItemStack, ItemStack, Inventory> {
073
074  private static volatile BukkitItemPlatform instance;
075
076  protected final VanillaProvider defaultProvider = new VanillaProvider();
077  protected final BukkitCalculationsProvider calculationsProvider = new BukkitCalculationsProvider();
078
079  private BukkitItemPlatform() {
080
081    super();
082  }
083
084  @Override
085  public BukkitItemStack createStack(final String material) {
086    return new BukkitItemStack().of(material, 1);
087  }
088
089  public static BukkitItemPlatform instance() {
090
091    final BukkitItemPlatform result = instance;
092    if(result != null) {
093      return result;
094    }
095
096    synchronized(BukkitItemPlatform.class) {
097
098      if(instance == null) {
099
100        instance = new BukkitItemPlatform();
101        instance.addDefaults();
102      }
103      return instance;
104    }
105  }
106
107  /**
108   * @return the version that is being used currently
109   * @since 0.2.0.0
110   */
111  @Override
112  public String version() {
113    return Bukkit.getServer().getBukkitVersion().split("-")[0];
114  }
115
116  @Override
117  public void addDefaults() {
118
119    registerConversions();
120
121    //bukkit base implementation.
122    addMulti(new BukkitBundleComponent());
123    addMulti(new BukkitContainerComponent());
124    addMulti(new BukkitCustomNameComponent());
125    addMulti(new BukkitDamageComponent());
126    addMulti(new BukkitEnchantmentsComponent());
127    addMulti(new BukkitItemModelComponent());
128    addMulti(new BukkitItemNameComponent());
129    addMulti(new BukkitLoreComponent());
130    addMulti(new BukkitMaxStackSizeComponent());
131    addMulti(new BukkitModelDataComponent());
132    addMulti(new BukkitModelDataOldComponent());
133    addMulti(new BukkitProfileComponent());
134    addMulti(new BukkitShulkerColorComponent());
135
136
137    if(Bukkit.getPluginManager().isPluginEnabled("ItemsAdder")) {
138      addItemProvider(new ItemAdderProvider());
139    }
140
141    if(Bukkit.getPluginManager().isPluginEnabled("MythicMobs")) {
142      addItemProvider(new MMOItemProvider());
143    }
144
145    if(Bukkit.getPluginManager().isPluginEnabled("Nexo")) {
146      addItemProvider(new NexoProvider());
147    }
148
149    if(Bukkit.getPluginManager().isPluginEnabled("Nova")) {
150      addItemProvider(new NovaProvider());
151    }
152
153    if(Bukkit.getPluginManager().isPluginEnabled("Oraxen")) {
154      addItemProvider(new OraxenProvider());
155    }
156
157    if(Bukkit.getPluginManager().isPluginEnabled("Slimefun")) {
158      addItemProvider(new SlimefunProvider());
159    }
160
161
162    addItemProvider(defaultProvider);
163
164    System.out.println("Item Providers: " + itemProviders.size());
165
166  }
167
168  /**
169   * Retrieves the default provider for the item stack comparison.
170   *
171   * @return the default provider for the item stack comparison.
172   * @since 0.2.0.0
173   */
174  @Override
175  public @NotNull ItemProvider<ItemStack> defaultProvider() {
176
177    return defaultProvider;
178  }
179
180  /**
181   * Retrieves the identifier of the default provider for the item stack comparison.
182   *
183   * @return The identifier of the default provider for the item stack comparison.
184   * @since 0.2.0.0
185   */
186  @Override
187  public @NotNull String defaultProviderIdentifier() {
188
189    return defaultProvider.identifier();
190  }
191
192  @Override
193  public BukkitCalculationsProvider calculations() {
194    return calculationsProvider;
195  }
196
197  /**
198   * Converts the given locale stack to an instance of {@link AbstractItemStack}
199   *
200   * @param locale the locale to convert
201   *
202   * @return the converted locale of type I
203   * @since 0.2.0.0
204   */
205  @Override
206  public BukkitItemStack locale(final ItemStack locale) {
207
208    return new BukkitItemStack().of(locale);
209  }
210
211  @SuppressWarnings({"deprecation", "UnstableApiUsage" })
212  private void registerConversions() {
213
214    //RegisterConversion for EquipmentSlot
215    converter.registerConversion(String.class, EquipmentSlot.class, input -> {
216      switch (input.toUpperCase()) {
217        case "HAND": return EquipmentSlot.HAND;
218        case "OFF_HAND": return EquipmentSlot.OFF_HAND;
219        case "FEET": return EquipmentSlot.FEET;
220        case "LEGS": return EquipmentSlot.LEGS;
221        case "CHEST": return EquipmentSlot.CHEST;
222        case "HEAD": return EquipmentSlot.HEAD;
223        case "BODY": return EquipmentSlot.BODY;
224        default: return EquipmentSlot.HAND;
225      }
226    });
227
228    converter.registerConversion(EquipmentSlot.class, String.class, input->switch(input) {
229      case HAND -> "HAND";
230      case OFF_HAND -> "OFF_HAND";
231      case FEET -> "FEET";
232      case LEGS -> "LEGS";
233      case CHEST -> "CHEST";
234      case HEAD -> "HEAD";
235      case BODY -> "BODY";
236      case SADDLE -> "SADDLE";
237      default -> "HAND";
238    });
239
240    //RegisterConversion for EquipmentSlotGroup
241    converter.registerConversion(String.class, EquipmentSlotGroup.class, input -> {
242      final EquipmentSlotGroup group = EquipmentSlotGroup.getByName(input);
243      if(group == null) {
244        throw new IllegalArgumentException("Unknown input: " + input);
245      }
246      return group;
247    });
248
249    converter.registerConversion(EquipmentSlotGroup.class, String.class, EquipmentSlotGroup::toString);
250
251    //Register Conversions for AttributeModifier
252    converter.registerConversion(String.class, AttributeModifier.Operation.class, input->switch(input.toLowerCase()) {
253      case "add_value" -> AttributeModifier.Operation.ADD_NUMBER;
254      case "add_multiplied_base" -> AttributeModifier.Operation.ADD_SCALAR;
255      case "add_multiplied_total" -> AttributeModifier.Operation.MULTIPLY_SCALAR_1;
256      default -> throw new IllegalArgumentException("Unknown input: " + input);
257    });
258
259    converter.registerConversion(AttributeModifier.Operation.class, String.class, input ->switch(input) {
260      case ADD_NUMBER -> "add_value";
261      case ADD_SCALAR -> "add_multiplied_base";
262      case MULTIPLY_SCALAR_1 -> "add_multiplied_total";
263    });
264
265    //Register conversions for DyeColor
266    converter.registerConversion(String.class, DyeColor.class, input ->switch(input.toLowerCase(Locale.ROOT)) {
267      case "white" -> DyeColor.WHITE;
268      case "orange" -> DyeColor.ORANGE;
269      case "magenta" -> DyeColor.MAGENTA;
270      case "light_blue" -> DyeColor.LIGHT_BLUE;
271      case "yellow" -> DyeColor.YELLOW;
272      case "lime" -> DyeColor.LIME;
273      case "pink" -> DyeColor.PINK;
274      case "gray" -> DyeColor.GRAY;
275      case "light_gray" -> DyeColor.LIGHT_GRAY;
276      case "cyan" -> DyeColor.CYAN;
277      case "purple" -> DyeColor.PURPLE;
278      case "blue" -> DyeColor.BLUE;
279      case "brown" -> DyeColor.BROWN;
280      case "green" -> DyeColor.GREEN;
281      case "red" -> DyeColor.RED;
282      case "black" -> DyeColor.BLACK;
283      default -> throw new IllegalArgumentException("Unknown DyeColor: " + input);
284    });
285
286    converter.registerConversion(DyeColor.class, String.class, input ->switch(input) {
287      case WHITE -> "white";
288      case ORANGE -> "orange";
289      case MAGENTA -> "magenta";
290      case LIGHT_BLUE -> "light_blue";
291      case YELLOW -> "yellow";
292      case LIME -> "lime";
293      case PINK -> "pink";
294      case GRAY -> "gray";
295      case LIGHT_GRAY -> "light_gray";
296      case CYAN -> "cyan";
297      case PURPLE -> "purple";
298      case BLUE -> "blue";
299      case BROWN -> "brown";
300      case GREEN -> "green";
301      case RED -> "red";
302      case BLACK -> "black";
303    });
304
305    //Register Conversions for PatternType, which will be dependent on versions
306    //We'll separate the legacy find methods from the modern ones in order to maintain one component
307    // class for both.
308    if(VersionUtil.isOneTwentyOne(version())) {
309
310      converter.registerConversion(String.class, PatternType.class, input->{
311
312        final NamespacedKey key = NamespacedKey.fromString(input);
313        if(key != null) {
314
315          final PatternType patternType = Registry.BANNER_PATTERN.get(key);
316          if(patternType != null) {
317
318            return patternType;
319          }
320        }
321        throw new IllegalArgumentException("Unknown PatternType: " + input);
322      });
323
324      if(VersionUtil.isOneTwentyOneFour(version())) {
325
326        converter.registerConversion(PatternType.class, String.class, input->{
327
328          final NamespacedKey key = input.getKeyOrThrow();
329
330          return key.toString();
331        });
332      } else {
333
334        converter.registerConversion(PatternType.class, String.class, input->{
335
336          final NamespacedKey key = input.getKey();
337
338          return key.toString();
339        });
340      }
341
342    } else {
343
344      converter.registerConversion(String.class, PatternType.class, PatternType::valueOf);
345
346      converter.registerConversion(PatternType.class, String.class, PatternType::getIdentifier);
347    }
348
349    //Register Conversions for Enchantment, which will be dependent on versions
350    //We'll separate the legacy find methods from the modern ones in order to maintain one component
351    // class for both.
352    if(VersionUtil.isOneTwentyOneFour(version())) {
353
354      converter.registerConversion(String.class, Enchantment.class, (final String input)->{
355
356        final NamespacedKey key = NamespacedKey.fromString(input);
357        if(key != null) {
358
359          return Registry.ENCHANTMENT.getOrThrow(key);
360        }
361        throw new IllegalArgumentException("Unknown Enchantment: " + input);
362      });
363
364      converter.registerConversion(Enchantment.class, String.class, (final Enchantment input)->input.getKeyOrThrow().toString());
365
366    } else if(VersionUtil.isOneThirteen(version())) {
367      converter.registerConversion(String.class, Enchantment.class, (final String input)->{
368
369        final Enchantment enchantment = Enchantment.getByKey(NamespacedKey.fromString(input));
370        if(enchantment == null) {
371
372          throw new IllegalArgumentException("Unknown Enchantment: " + input);
373        }
374        return enchantment;
375      });
376
377      converter.registerConversion(Enchantment.class, String.class, (final Enchantment input)->input.getKey().getKey());
378    } else {
379
380      converter.registerConversion(String.class, Enchantment.class, (final String input)->{
381
382        final Enchantment enchantment = Enchantment.getByName(input);
383        if(enchantment == null) {
384
385          throw new IllegalArgumentException("Unknown Enchantment: " + input);
386        }
387        return enchantment;
388      });
389
390      converter.registerConversion(Enchantment.class, String.class, Enchantment::getName);
391    }
392
393    //Register Conversions for Trim Material, which will be dependent on versions
394    //We'll separate the legacy find methods from the modern ones in order to maintain one component
395    // class for both.
396    if(VersionUtil.isOneTwentyOneFour(version())) {
397
398      converter.registerConversion(TrimMaterial.class, String.class, input->input.getKeyOrThrow().toString());
399
400      converter.registerConversion(String.class, TrimMaterial.class, input->{
401        final NamespacedKey key = NamespacedKey.fromString(input);
402        if(key != null) {
403
404          return Registry.TRIM_MATERIAL.getOrThrow(key);
405        }
406        throw new IllegalArgumentException("Unknown TrimMaterial: " + input);
407      });
408
409    } else if(VersionUtil.isOneTwenty(version())) {
410
411      converter.registerConversion(TrimMaterial.class, String.class, input->input.getKey().toString());
412
413      converter.registerConversion(String.class, TrimMaterial.class, input->{
414        final NamespacedKey key = NamespacedKey.fromString(input);
415        if(key != null) {
416
417          return Registry.TRIM_MATERIAL.get(key);
418        }
419        throw new IllegalArgumentException("Unknown TrimMaterial: " + input);
420      });
421    }
422
423    //Register Conversions for NamespacedKey, which will be dependent on versions
424    //We'll separate the legacy find methods from the modern ones in order to maintain one component
425    // class for both.
426    converter.registerConversion(ItemRarity.class, String.class, input->switch(input) {
427      case EPIC -> "epic";
428      case RARE -> "rare";
429      case UNCOMMON -> "uncommon";
430      default -> "common";
431    });
432
433    converter.registerConversion(String.class, ItemRarity.class, input->switch(input.toLowerCase()) {
434      case "epic" -> ItemRarity.EPIC;
435      case "rare" -> ItemRarity.RARE;
436      case "uncommon" -> ItemRarity.UNCOMMON;
437      default -> ItemRarity.COMMON;
438    });
439
440    //Register Conversions for Trim Pattern, which will be dependent on versions
441    //We'll separate the legacy find methods from the modern ones in order to maintain one component
442    // class for both.
443    if(VersionUtil.isOneTwentyOneFour(version())) {
444
445      converter.registerConversion(TrimPattern.class, String.class, input->input.getKeyOrThrow().toString());
446
447      converter.registerConversion(String.class, TrimPattern.class, input->{
448        final NamespacedKey key = NamespacedKey.fromString(input);
449        if(key != null) {
450
451          return Registry.TRIM_PATTERN.getOrThrow(key);
452        }
453        throw new IllegalArgumentException("Unknown TrimPattern: " + input);
454      });
455
456    } else if(VersionUtil.isOneTwenty(version())) {
457
458      converter.registerConversion(TrimPattern.class, String.class, input->input.getKey().toString());
459
460      converter.registerConversion(String.class, TrimPattern.class, input->{
461        final NamespacedKey key = NamespacedKey.fromString(input);
462        if(key != null) {
463
464          return Registry.TRIM_PATTERN.get(key);
465        }
466        throw new IllegalArgumentException("Unknown TrimPattern: " + input);
467      });
468    }
469
470    if(VersionUtil.isOneTwentyOneFour(version())) {
471
472      converter.registerConversion(PotionEffectType.class, String.class, input->input.getKeyOrThrow().toString());
473      converter.registerConversion(String.class, PotionEffectType.class, input->{
474        final NamespacedKey key = NamespacedKey.fromString(input);
475        if(key != null) {
476
477          return Registry.EFFECT.getOrThrow(key);
478        }
479        throw new IllegalArgumentException("Unknown PotionEffectType: " + input);
480      });
481    } else {
482
483      converter.registerConversion(PotionEffectType.class, String.class, PotionEffectType::getName);
484      converter.registerConversion(String.class, PotionEffectType.class, input->{
485
486        final PotionEffectType type = PotionEffectType.getByName(input);
487        if(type != null) {
488
489          return type;
490        }
491        throw new IllegalArgumentException("Unknown PotionEffectType: " + input);
492      });
493    }
494  }
495
496  /**
497   * Initializes and returns an AbstractItemStack object by deserializing the provided JSON object.
498   *
499   * @param object the JSON object to deserialize
500   *
501   * @return an initialized AbstractItemStack object
502   * @since 0.2.0.0
503   */
504  @Override
505  public Optional<BukkitItemStack> initSerialized(final JSONObject object) {
506
507    try {
508      return Optional.ofNullable(new BukkitItemStack().of(object));
509    } catch(final ParseException e) {
510      return Optional.empty();
511    }
512  }
513}