diff --git a/module/items-integration/build.gradle.kts b/module/items-integration/build.gradle.kts new file mode 100644 index 00000000..0ab6609b --- /dev/null +++ b/module/items-integration/build.gradle.kts @@ -0,0 +1,17 @@ +repositories { + maven("https://repo.oraxen.com/releases") + maven("https://repo.codemc.io/repository/maven-public/") + maven("https://jitpack.io") + maven("https://mvn.lumine.io/repository/maven-public/") { + metadataSources { + artifact() + } + } +} + +dependencies { + compileOnly("io.th0rgal:oraxen:1.173.0") + compileOnly("me.zombie_striker:QualityArmory:2.0.17") + compileOnly("com.github.LoneDev6:api-itemsadder:3.6.1") + compileOnly("io.lumine:Mythic-Dist:5.6.1") +} diff --git a/module/items-integration/src/main/java/org/battleplugins/arena/module/items/ItemsIntegration.java b/module/items-integration/src/main/java/org/battleplugins/arena/module/items/ItemsIntegration.java new file mode 100644 index 00000000..f2dada8b --- /dev/null +++ b/module/items-integration/src/main/java/org/battleplugins/arena/module/items/ItemsIntegration.java @@ -0,0 +1,48 @@ +package org.battleplugins.arena.module.items; + +import org.battleplugins.arena.event.BattleArenaPostInitializeEvent; +import org.battleplugins.arena.feature.items.Items; +import org.battleplugins.arena.module.ArenaModule; +import org.battleplugins.arena.module.ArenaModuleInitializer; +import org.battleplugins.arena.module.items.itemsadder.ItemsAdderFeature; +import org.battleplugins.arena.module.items.mythiccrucible.MythicCrucibleFeature; +import org.battleplugins.arena.module.items.oraxen.OraxenFeature; +import org.battleplugins.arena.module.items.qualityarmory.QualityArmoryFeature; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; + +/** + * A module that allows for hooking into various item provider plugins. + */ +@ArenaModule(id = ItemsIntegration.ID, priority = 100, name = "Items", description = "Adds support for hooking into various item provider plugins.", authors = "BattlePlugins") +public class ItemsIntegration implements ArenaModuleInitializer { + public static final String ID = "items"; + + @EventHandler(priority = EventPriority.LOWEST) // Load before all other modules listening on this event + public void onPostInitialize(BattleArenaPostInitializeEvent event) { + if (Bukkit.getPluginManager().isPluginEnabled("QualityArmory")) { + Items.register(new QualityArmoryFeature()); + + event.getBattleArena().info("QualityArmory found. Registering item integration."); + } + + if (Bukkit.getPluginManager().isPluginEnabled("Oraxen")) { + Items.register(new OraxenFeature()); + + event.getBattleArena().info("Oraxen found. Registering item integration."); + } + + if (Bukkit.getPluginManager().isPluginEnabled("ItemsAdder")) { + Items.register(new ItemsAdderFeature()); + + event.getBattleArena().info("ItemsAdder found. Registering item integration."); + } + + if (Bukkit.getPluginManager().isPluginEnabled("MythicCrucible")) { + Items.register(new MythicCrucibleFeature()); + + event.getBattleArena().info("MythicCrucible found. Registering item integration."); + } + } +} diff --git a/module/items-integration/src/main/java/org/battleplugins/arena/module/items/itemsadder/ItemsAdderFeature.java b/module/items-integration/src/main/java/org/battleplugins/arena/module/items/itemsadder/ItemsAdderFeature.java new file mode 100644 index 00000000..3c892a83 --- /dev/null +++ b/module/items-integration/src/main/java/org/battleplugins/arena/module/items/itemsadder/ItemsAdderFeature.java @@ -0,0 +1,20 @@ +package org.battleplugins.arena.module.items.itemsadder; + +import dev.lone.itemsadder.api.CustomStack; +import org.battleplugins.arena.feature.PluginFeature; +import org.battleplugins.arena.feature.items.ItemsFeature; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; + +public class ItemsAdderFeature extends PluginFeature implements ItemsFeature { + + public ItemsAdderFeature() { + super("ItemsAdder"); + } + + @Override + public ItemStack createItem(NamespacedKey key) { + CustomStack customStack = CustomStack.getInstance(key.value()); + return customStack == null ? null : customStack.getItemStack(); + } +} diff --git a/module/items-integration/src/main/java/org/battleplugins/arena/module/items/mythiccrucible/MythicCrucibleFeature.java b/module/items-integration/src/main/java/org/battleplugins/arena/module/items/mythiccrucible/MythicCrucibleFeature.java new file mode 100644 index 00000000..ac655695 --- /dev/null +++ b/module/items-integration/src/main/java/org/battleplugins/arena/module/items/mythiccrucible/MythicCrucibleFeature.java @@ -0,0 +1,22 @@ +package org.battleplugins.arena.module.items.mythiccrucible; + +import io.lumine.mythic.bukkit.BukkitAdapter; +import io.lumine.mythic.bukkit.MythicBukkit; +import org.battleplugins.arena.feature.PluginFeature; +import org.battleplugins.arena.feature.items.ItemsFeature; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; + +public class MythicCrucibleFeature extends PluginFeature implements ItemsFeature { + + public MythicCrucibleFeature() { + super("MythicCrucible"); + } + + @Override + public ItemStack createItem(NamespacedKey key) { + return MythicBukkit.inst().getItemManager().getItem(key.value()) + .map(item -> BukkitAdapter.adapt(item.generateItemStack(1))) + .orElse(null); + } +} diff --git a/module/items-integration/src/main/java/org/battleplugins/arena/module/items/oraxen/OraxenFeature.java b/module/items-integration/src/main/java/org/battleplugins/arena/module/items/oraxen/OraxenFeature.java new file mode 100644 index 00000000..39d930df --- /dev/null +++ b/module/items-integration/src/main/java/org/battleplugins/arena/module/items/oraxen/OraxenFeature.java @@ -0,0 +1,22 @@ +package org.battleplugins.arena.module.items.oraxen; + +import io.th0rgal.oraxen.api.OraxenItems; +import io.th0rgal.oraxen.items.ItemBuilder; +import org.battleplugins.arena.feature.PluginFeature; +import org.battleplugins.arena.feature.items.ItemsFeature; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; + +public class OraxenFeature extends PluginFeature implements ItemsFeature { + + public OraxenFeature() { + super("Oraxen"); + } + + @Override + public ItemStack createItem(NamespacedKey key) { + return OraxenItems.getOptionalItemById(key.value()) + .map(ItemBuilder::build) + .orElse(null); + } +} diff --git a/module/items-integration/src/main/java/org/battleplugins/arena/module/items/qualityarmory/QualityArmoryFeature.java b/module/items-integration/src/main/java/org/battleplugins/arena/module/items/qualityarmory/QualityArmoryFeature.java new file mode 100644 index 00000000..f0260662 --- /dev/null +++ b/module/items-integration/src/main/java/org/battleplugins/arena/module/items/qualityarmory/QualityArmoryFeature.java @@ -0,0 +1,19 @@ +package org.battleplugins.arena.module.items.qualityarmory; + +import me.zombie_striker.qg.api.QualityArmory; +import org.battleplugins.arena.feature.PluginFeature; +import org.battleplugins.arena.feature.items.ItemsFeature; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; + +public class QualityArmoryFeature extends PluginFeature implements ItemsFeature { + + public QualityArmoryFeature() { + super("QualityArmory"); + } + + @Override + public ItemStack createItem(NamespacedKey key) { + return QualityArmory.getCustomItemAsItemStack(key.value()); + } +} diff --git a/plugin/src/main/java/org/battleplugins/arena/config/ItemStackParser.java b/plugin/src/main/java/org/battleplugins/arena/config/ItemStackParser.java index 107d8365..242d936f 100644 --- a/plugin/src/main/java/org/battleplugins/arena/config/ItemStackParser.java +++ b/plugin/src/main/java/org/battleplugins/arena/config/ItemStackParser.java @@ -3,6 +3,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.minimessage.MiniMessage; +import org.battleplugins.arena.feature.items.Items; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; @@ -15,12 +16,14 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.potion.PotionEffect; +import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.function.BiConsumer; @DocumentationSource("https://docs.battleplugins.org/books/user-guide/page/item-syntax") public final class ItemStackParser implements ArenaConfigParser.Parser { @@ -43,8 +46,6 @@ public ItemStack parse(Object object) throws ParseException { } public static ItemStack deserializeSingular(String contents) throws ParseException { - ItemStack itemStack; - SingularValueParser.ArgumentBuffer buffer; try { buffer = SingularValueParser.parseNamed(contents, SingularValueParser.BraceStyle.CURLY, ';'); @@ -67,21 +68,32 @@ public static ItemStack deserializeSingular(String contents) throws ParseExcepti SingularValueParser.Argument root = buffer.pop(); if (root.key().equals("root")) { - Material material = Material.matchMaterial(root.value()); - if (material == null) { - throw new ParseException("Invalid material " + root.value()) - .cause(ParseException.Cause.INVALID_VALUE) - .type(ItemStackParser.class) - .userError(); - } - - itemStack = new ItemStack(material); + return Items.createItem(NamespacedKey.fromString(root.value()), buffer); } else { throw new ParseException("Invalid ItemStack root tag " + root.key()) .cause(ParseException.Cause.INTERNAL_ERROR) .type(ItemStackParser.class); } + } + + public static ItemStack deserializeSingularVanilla(NamespacedKey itemType, SingularValueParser.ArgumentBuffer buffer) throws ParseException { + Material material = Material.matchMaterial(itemType.toString()); + if (material == null) { + throw new ParseException("Invalid material " + itemType) + .cause(ParseException.Cause.INVALID_VALUE) + .type(ItemStackParser.class) + .userError(); + } + ItemStack itemStack = new ItemStack(material); + return applyItemProperties(itemStack, buffer); + } + + public static ItemStack applyItemProperties(ItemStack itemStack, SingularValueParser.ArgumentBuffer buffer) throws ParseException { + return applyItemProperties(itemStack, buffer, null); + } + + public static ItemStack applyItemProperties(ItemStack itemStack, SingularValueParser.ArgumentBuffer buffer, @Nullable BiConsumer unknownArgumentConsumer) throws ParseException { ItemMeta itemMeta = itemStack.getItemMeta(); while (buffer.hasNext()) { SingularValueParser.Argument argument = buffer.pop(); @@ -151,6 +163,12 @@ public static ItemStack deserializeSingular(String contents) throws ParseExcepti potionMeta.addCustomEffect(potionEffect, true); } } + + default -> { + if (unknownArgumentConsumer != null) { + unknownArgumentConsumer.accept(itemMeta, argument); + } + } } } diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/FeatureController.java b/plugin/src/main/java/org/battleplugins/arena/feature/FeatureController.java index 3fd54f06..abb9a525 100644 --- a/plugin/src/main/java/org/battleplugins/arena/feature/FeatureController.java +++ b/plugin/src/main/java/org/battleplugins/arena/feature/FeatureController.java @@ -46,7 +46,7 @@ private static T getBestFeature(Class clazz) { } @SuppressWarnings("unchecked") - private static List getFeatures(Class clazz) { + protected static List getFeatures(Class clazz) { return (List) FEATURES.getOrDefault(clazz, List.of()); } } diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/PluginFeature.java b/plugin/src/main/java/org/battleplugins/arena/feature/PluginFeature.java index f72cf690..99ee572e 100644 --- a/plugin/src/main/java/org/battleplugins/arena/feature/PluginFeature.java +++ b/plugin/src/main/java/org/battleplugins/arena/feature/PluginFeature.java @@ -26,4 +26,13 @@ public PluginFeature(Plugin plugin) { public boolean isEnabled() { return this.plugin.isEnabled(); } + + /** + * Gets the plugin that this feature is implemented by. + * + * @return the plugin that this feature is implemented by + */ + public Plugin getPlugin() { + return this.plugin; + } } diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/hologram/Holograms.java b/plugin/src/main/java/org/battleplugins/arena/feature/hologram/Holograms.java index 4fdaf359..3e97c280 100644 --- a/plugin/src/main/java/org/battleplugins/arena/feature/hologram/Holograms.java +++ b/plugin/src/main/java/org/battleplugins/arena/feature/hologram/Holograms.java @@ -11,7 +11,7 @@ * API for holograms used in BattleArena. */ @ApiStatus.Experimental -public class Holograms extends FeatureController { +public final class Holograms extends FeatureController { private static HologramFeature instance; /** @@ -42,6 +42,11 @@ public static void removeHologram(Hologram hologram) { } } + /** + * Gets the instance of the {@link HologramFeature}. + * + * @return the instance of the {@link HologramFeature} + */ @Nullable private static HologramFeature instance() { if (instance == null) { @@ -51,6 +56,11 @@ private static HologramFeature instance() { return instance; } + /** + * Registers a {@link HologramFeature} to the feature controller. + * + * @param feature the feature to register + */ public static void register(HologramFeature feature) { registerFeature(HologramFeature.class, feature); } diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/items/Items.java b/plugin/src/main/java/org/battleplugins/arena/feature/items/Items.java new file mode 100644 index 00000000..9a627496 --- /dev/null +++ b/plugin/src/main/java/org/battleplugins/arena/feature/items/Items.java @@ -0,0 +1,64 @@ +package org.battleplugins.arena.feature.items; + +import org.battleplugins.arena.config.SingularValueParser; +import org.battleplugins.arena.feature.FeatureController; +import org.battleplugins.arena.feature.PluginFeature; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; + +import java.util.List; +import java.util.Locale; + +/** + * API for items used in BattleArena. + *

+ * This API serves as a service provider to allow for + * accessing and creating items from Minecraft as well + * as third parties. + */ +@ApiStatus.Experimental +public final class Items extends FeatureController> { + + /** + * Creates an {@link ItemStack} from the given key and arguments. + * + * @param key the key of the item + * @param arguments the arguments to create the item with + * @return the created item + */ + public static ItemStack createItem(NamespacedKey key, SingularValueParser.ArgumentBuffer arguments) { + return getFeature(key).createItem(key, arguments); + } + + /** + * Registers a {@link ItemsFeature} to the feature controller. + * + * @param feature the feature to register + */ + public static & ItemsFeature> void register(T feature) { + registerFeature(ItemsFeature.class, feature); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static & ItemsFeature> ItemsFeature getFeature(NamespacedKey key) { + // Fast-track vanilla + if (key.getNamespace().equals(NamespacedKey.MINECRAFT)) { + return VanillaItemsFeature.INSTANCE; + } + + List features = (List) getFeatures(ItemsFeature.class); + for (T feature : features) { + if (!feature.isEnabled()) { + continue; + } + + String pluginNamespace = feature.getPlugin().getName().toLowerCase(Locale.ROOT); + if (key.getNamespace().equals(pluginNamespace)) { + return feature; + } + } + + return VanillaItemsFeature.INSTANCE; + } +} diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/items/ItemsFeature.java b/plugin/src/main/java/org/battleplugins/arena/feature/items/ItemsFeature.java new file mode 100644 index 00000000..9b705646 --- /dev/null +++ b/plugin/src/main/java/org/battleplugins/arena/feature/items/ItemsFeature.java @@ -0,0 +1,54 @@ +package org.battleplugins.arena.feature.items; + +import org.battleplugins.arena.BattleArena; +import org.battleplugins.arena.config.ItemStackParser; +import org.battleplugins.arena.config.ParseException; +import org.battleplugins.arena.config.SingularValueParser; +import org.battleplugins.arena.feature.FeatureInstance; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.ApiStatus; + +/** + * Main entrypoint items holograms. Implementing plugins + * should implement this interface to provide custom item + * support. + */ +@ApiStatus.Experimental +public interface ItemsFeature extends FeatureInstance { + + /** + * Creates an {@link ItemStack} from the given key. + * + * @param key the key of the item + * @return the created item + */ + ItemStack createItem(NamespacedKey key); + + /** + * Creates an {@link ItemStack} from the given key and arguments. + * + * @param key the key of the item + * @param arguments the arguments to create the item with + * @return the created item + */ + default ItemStack createItem(NamespacedKey key, SingularValueParser.ArgumentBuffer arguments) { + try { + ItemStack itemStack = this.createItem(key); + if (itemStack == null) { + BattleArena.getInstance().error("Failed to create item from key {}", key); + return new ItemStack(Material.AIR); + } + + return ItemStackParser.applyItemProperties(itemStack, arguments, (itemMeta, argument) -> this.onUnknownArgument(itemStack, itemMeta, argument.key(), argument.value())); + } catch (ParseException e) { + ParseException.handle(e); + return new ItemStack(Material.AIR); + } + } + + default void onUnknownArgument(ItemStack itemStack, ItemMeta itemMeta, String key, String value) { + } +} diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/items/VanillaItemsFeature.java b/plugin/src/main/java/org/battleplugins/arena/feature/items/VanillaItemsFeature.java new file mode 100644 index 00000000..9c1d16d3 --- /dev/null +++ b/plugin/src/main/java/org/battleplugins/arena/feature/items/VanillaItemsFeature.java @@ -0,0 +1,35 @@ +package org.battleplugins.arena.feature.items; + +import org.battleplugins.arena.config.ItemStackParser; +import org.battleplugins.arena.config.ParseException; +import org.battleplugins.arena.config.SingularValueParser; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; + +class VanillaItemsFeature implements ItemsFeature { + static final VanillaItemsFeature INSTANCE = new VanillaItemsFeature(); + + private VanillaItemsFeature() { + } + + @Override + public ItemStack createItem(NamespacedKey key) { + throw new UnsupportedOperationException("Cannot create vanilla item without arguments"); + } + + @Override + public ItemStack createItem(NamespacedKey key, SingularValueParser.ArgumentBuffer arguments) { + try { + return ItemStackParser.deserializeSingularVanilla(key, arguments); + } catch (ParseException e) { + ParseException.handle(e); + return new ItemStack(Material.AIR); + } + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/plugin/src/main/java/org/battleplugins/arena/feature/party/Parties.java b/plugin/src/main/java/org/battleplugins/arena/feature/party/Parties.java index 0429b476..8cafe606 100644 --- a/plugin/src/main/java/org/battleplugins/arena/feature/party/Parties.java +++ b/plugin/src/main/java/org/battleplugins/arena/feature/party/Parties.java @@ -7,10 +7,10 @@ import java.util.UUID; /** - * API for holograms used in Parties. + * API for parties used in BattleArena. */ @ApiStatus.Experimental -public class Parties extends FeatureController { +public final class Parties extends FeatureController { private static PartiesFeature instance; /** @@ -29,6 +29,11 @@ public static Party getParty(UUID uuid) { return instance.getParty(uuid); } + /** + * Gets the instance of the {@link PartiesFeature}. + * + * @return the instance of the {@link PartiesFeature} + */ public static PartiesFeature instance() { if (instance == null) { instance = createInstance(PartiesFeature.class); @@ -37,6 +42,11 @@ public static PartiesFeature instance() { return instance; } + /** + * Registers a {@link PartiesFeature} to the feature controller. + * + * @param feature the feature to register + */ public static void register(PartiesFeature feature) { registerFeature(PartiesFeature.class, feature); } diff --git a/plugin/src/main/java/org/battleplugins/arena/module/ArenaModule.java b/plugin/src/main/java/org/battleplugins/arena/module/ArenaModule.java index 83cc5af0..499359be 100644 --- a/plugin/src/main/java/org/battleplugins/arena/module/ArenaModule.java +++ b/plugin/src/main/java/org/battleplugins/arena/module/ArenaModule.java @@ -48,4 +48,11 @@ * @return the authors of the module */ String[] authors() default {}; + + /** + * The priority of the module. + * + * @return the priority of the module + */ + int priority() default 0; } diff --git a/plugin/src/main/java/org/battleplugins/arena/module/ArenaModuleLoader.java b/plugin/src/main/java/org/battleplugins/arena/module/ArenaModuleLoader.java index e41e222a..fd7efb3f 100644 --- a/plugin/src/main/java/org/battleplugins/arena/module/ArenaModuleLoader.java +++ b/plugin/src/main/java/org/battleplugins/arena/module/ArenaModuleLoader.java @@ -11,6 +11,7 @@ import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -110,7 +111,11 @@ public void loadModules() throws IOException { } public void enableModules() { - this.modules.values().forEach(module -> { + Collection> modules = this.modules.values().stream() + .sorted((module1, module2) -> Integer.compare(module2.module().priority(), module1.module().priority())) + .toList(); + + modules.forEach(module -> { if (this.plugin.getMainConfig().getDisabledModules().contains(module.module().id())) { this.plugin.debug("Module {} is disabled in the configuration. Skipping...", module.module().name()); return; diff --git a/settings.gradle.kts b/settings.gradle.kts index c90f5542..c5b351e6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,6 +24,7 @@ include("module:boundary-enforcer") include("module:classes") include("module:duels") include("module:hologram-integration") +include("module:items-integration") include("module:one-in-the-chamber") include("module:party-integration") include("module:placeholderapi-integration")