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}