diff --git a/eco-api/src/main/java/com/willfp/eco/core/blocks/Blocks.java b/eco-api/src/main/java/com/willfp/eco/core/blocks/Blocks.java new file mode 100644 index 000000000..1e75d5dd2 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/blocks/Blocks.java @@ -0,0 +1,149 @@ +package com.willfp.eco.core.blocks; + +import com.willfp.eco.core.blocks.impl.EmptyTestableBlock; +import com.willfp.eco.core.blocks.impl.MaterialTestableBlock; +import com.willfp.eco.core.blocks.impl.UnrestrictedMaterialTestableBlock; +import com.willfp.eco.core.blocks.provider.BlockProvider; +import com.willfp.eco.util.NamespacedKeyUtils; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Class to manage all custom and vanilla blocks. + */ +public final class Blocks { + /** + * All entities. + */ + private static final Map REGISTRY = new ConcurrentHashMap<>(); + + /** + * All block providers. + */ + private static final Map PROVIDERS = new ConcurrentHashMap<>(); + + /** + * The lookup handler. + */ + private static final BlocksLookupHandler BLOCKS_LOOKUP_HANDLER = new BlocksLookupHandler(Blocks::doParse); + + /** + * Register a new custom block. + * + * @param key The key of the block. + * @param block The block. + */ + public static void registerCustomBlock(@NotNull final NamespacedKey key, + @NotNull final TestableBlock block) { + REGISTRY.put(key, block); + } + + /** + * Register a new block provider. + * + * @param provider The provider. + */ + public static void registerBlockProvider(@NotNull final BlockProvider provider) { + PROVIDERS.put(provider.getNamespace(), provider); + } + + /** + * Remove a block. + * + * @param key The key of the block. + */ + public static void removeCustomBlock(@NotNull final NamespacedKey key) { + REGISTRY.remove(key); + } + + /** + * This is the backbone of the eco block system. + *

+ * You can look up a TestableBlock for any material or custom block, + * and it will return it. + *

+ * If you want to get a Block instance from this, then just call + * {@link TestableBlock#place(Location)}. + * + * @param key The lookup string. + * @return The testable block, or an empty testable block if not found. + */ + @NotNull + public static TestableBlock lookup(@NotNull final String key) { + return BLOCKS_LOOKUP_HANDLER.parseKey(key); + } + + @NotNull + private static TestableBlock doParse(@NotNull final String[] args) { + if (args.length == 0) { + return new EmptyTestableBlock(); + } + + String[] split = args[0].toLowerCase().split(":"); + if (split.length == 1) { + if (args[0].startsWith("*")) { + Material type = Material.getMaterial(args[0].substring(1)); + return (type == null) ? new EmptyTestableBlock() : new UnrestrictedMaterialTestableBlock(type); + } else { + Material type = Material.getMaterial(args[0].toUpperCase()); + return (type == null) ? new EmptyTestableBlock() : new MaterialTestableBlock(type); + } + } + + NamespacedKey namespacedKey = NamespacedKeyUtils.create(split[0], split[1]); + TestableBlock block = REGISTRY.get(namespacedKey); + + if (block != null) { + return block; + } + + BlockProvider provider = PROVIDERS.get(split[0]); + if (provider == null) { + return new EmptyTestableBlock(); + } + + block = provider.provideForKey(split[1]); + if (block == null) { + return new EmptyTestableBlock(); + } + + registerCustomBlock(namespacedKey, block); + return block; + } + + /** + * Get if block is a custom block. + * + * @param block The block to check. + * @return If is custom. + */ + public static boolean isCustomBlock(@NotNull final Block block) { + for (TestableBlock testable : REGISTRY.values()) { + if (testable.matches(block)) { + return true; + } + } + return false; + } + + /** + * Get all registered custom blocks. + * + * @return A set of all blocks. + */ + public static Set getCustomBlocks() { + return new HashSet<>(REGISTRY.values()); + } + + private Blocks() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/blocks/BlocksLookupHandler.java b/eco-api/src/main/java/com/willfp/eco/core/blocks/BlocksLookupHandler.java new file mode 100644 index 000000000..d08ecd6fd --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/blocks/BlocksLookupHandler.java @@ -0,0 +1,48 @@ +package com.willfp.eco.core.blocks; + +import com.willfp.eco.core.blocks.impl.EmptyTestableBlock; +import com.willfp.eco.core.blocks.impl.GroupedTestableBlocks; +import com.willfp.eco.core.lookup.LookupHandler; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.function.Function; + +/** + * Handle block lookup strings. + */ +public class BlocksLookupHandler implements LookupHandler { + /** + * The parser. + */ + private final Function parser; + + /** + * Create new lookup handler. + * + * @param parser The parser. + */ + public BlocksLookupHandler(@NotNull final Function parser) { + this.parser = parser; + } + + @Override + public @NotNull TestableBlock parse(@NotNull final String[] args) { + return parser.apply(args); + } + + @Override + public boolean validate(@NotNull final TestableBlock object) { + return !(object instanceof EmptyTestableBlock); + } + + @Override + public @NotNull TestableBlock getFailsafe() { + return new EmptyTestableBlock(); + } + + @Override + public @NotNull TestableBlock join(@NotNull final Collection options) { + return new GroupedTestableBlocks(options); + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/blocks/CustomBlock.java b/eco-api/src/main/java/com/willfp/eco/core/blocks/CustomBlock.java new file mode 100644 index 000000000..af2b60715 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/blocks/CustomBlock.java @@ -0,0 +1,84 @@ +package com.willfp.eco.core.blocks; + +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * A custom block has 3 components. + * + *

+ */ +public class CustomBlock implements TestableBlock { + /** + * The key. + */ + private final NamespacedKey key; + + /** + * The test for block to pass. + */ + private final Predicate<@NotNull Block> test; + + /** + * The provider to spawn the block. + */ + private final Function provider; + + /** + * Create a new custom block. + * + * @param key The block key. + * @param test The test. + * @param provider The provider to spawn the block. + */ + public CustomBlock(@NotNull final NamespacedKey key, + @NotNull final Predicate<@NotNull Block> test, + @NotNull final Function provider) { + this.key = key; + this.test = test; + this.provider = provider; + } + + @Override + public boolean matches(@Nullable final Block other) { + if (other == null) { + return false; + } + + return test.test(other); + } + + @Override + public @NotNull Block place(@NotNull final Location location) { + Validate.notNull(location.getWorld()); + + return provider.apply(location); + } + + /** + * Register the block. + */ + public void register() { + Blocks.registerCustomBlock(this.getKey(), this); + } + + /** + * Get the key. + * + * @return The key. + */ + public NamespacedKey getKey() { + return this.key; + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/blocks/TestableBlock.java b/eco-api/src/main/java/com/willfp/eco/core/blocks/TestableBlock.java new file mode 100644 index 000000000..aa16e0fe1 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/blocks/TestableBlock.java @@ -0,0 +1,31 @@ +package com.willfp.eco.core.blocks; + +import com.willfp.eco.core.lookup.Testable; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A block with a test. + */ +public interface TestableBlock extends Testable { + /** + * If a Block matches the test. + * + * @param other The other block. + * @return If the block matches. + */ + @Override + boolean matches(@Nullable Block other); + + /** + * Place the block. + * + * @param location The location. + * @return The block. + */ + @NotNull + Block place(@NotNull Location location); +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/EmptyTestableBlock.java b/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/EmptyTestableBlock.java new file mode 100644 index 000000000..190470eb4 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/EmptyTestableBlock.java @@ -0,0 +1,29 @@ +package com.willfp.eco.core.blocks.impl; + +import com.willfp.eco.core.blocks.TestableBlock; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Empty block. + */ +public class EmptyTestableBlock implements TestableBlock { + /** + * Create a new empty testable block. + */ + public EmptyTestableBlock() { + + } + + @Override + public boolean matches(@Nullable final Block other) { + return false; + } + + @Override + public @NotNull Block place(@NotNull final Location location) { + return location.getBlock(); + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/GroupedTestableBlocks.java b/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/GroupedTestableBlocks.java new file mode 100644 index 000000000..9a566b96d --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/GroupedTestableBlocks.java @@ -0,0 +1,60 @@ +package com.willfp.eco.core.blocks.impl; + +import com.willfp.eco.core.blocks.TestableBlock; +import com.willfp.eco.util.NumberUtils; +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * A group of testable blocks. + */ +public class GroupedTestableBlocks implements TestableBlock { + /** + * The children. + */ + private final Collection children; + + /** + * Create a new group of testable blocks. + * + * @param children The children. + */ + public GroupedTestableBlocks(@NotNull final Collection children) { + Validate.isTrue(!children.isEmpty(), "Group must have at least one child!"); + + this.children = children; + } + + @Override + public boolean matches(@Nullable final Block other) { + for (TestableBlock child : children) { + if (child.matches(other)) { + return true; + } + } + + return false; + } + + @Override + public @NotNull Block place(@NotNull final Location location) { + return new ArrayList<>(children) + .get(NumberUtils.randInt(0, children.size() - 1)) + .place(location); + } + + /** + * Get the children. + * + * @return The children. + */ + public Collection getChildren() { + return new ArrayList<>(children); + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/MaterialTestableBlock.java b/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/MaterialTestableBlock.java new file mode 100644 index 000000000..713e069c3 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/MaterialTestableBlock.java @@ -0,0 +1,59 @@ +package com.willfp.eco.core.blocks.impl; + +import com.willfp.eco.core.blocks.Blocks; +import com.willfp.eco.core.blocks.TestableBlock; +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A testable block for vanilla materials. + */ +public class MaterialTestableBlock implements TestableBlock { + /** + * The block type. + */ + private final Material material; + + /** + * Create a new unrestricted material testable block. + * + * @param material The material. + */ + public MaterialTestableBlock(@NotNull final Material material) { + this.material = material; + } + + @Override + public boolean matches(@Nullable final Block block) { + boolean simpleMatches = block != null && block.getType() == material; + + if (!simpleMatches) { + return false; + } + + return !Blocks.isCustomBlock(block); + } + + @Override + public @NotNull Block place(@NotNull Location location) { + Validate.notNull(location.getWorld()); + + Block block = location.getWorld().getBlockAt(location); + block.setType(material); + + return block; + } + + /** + * Get the material. + * + * @return The material. + */ + public Material getMaterial() { + return this.material; + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/UnrestrictedMaterialTestableBlock.java b/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/UnrestrictedMaterialTestableBlock.java new file mode 100644 index 000000000..498c07aba --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/blocks/impl/UnrestrictedMaterialTestableBlock.java @@ -0,0 +1,52 @@ +package com.willfp.eco.core.blocks.impl; + +import com.willfp.eco.core.blocks.TestableBlock; +import org.apache.commons.lang.Validate; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A testable block for materials regardless of data. + */ +public class UnrestrictedMaterialTestableBlock implements TestableBlock { + /** + * The block type. + */ + private final Material material; + + /** + * Create a new unrestricted material testable block. + * + * @param material The material. + */ + public UnrestrictedMaterialTestableBlock(@NotNull final Material material) { + this.material = material; + } + + @Override + public boolean matches(@Nullable final Block other) { + return other != null && other.getType() == material; + } + + @Override + public @NotNull Block place(@NotNull Location location) { + Validate.notNull(location.getWorld()); + + Block block = location.getWorld().getBlockAt(location); + block.setType(material); + + return block; + } + + /** + * Get the material. + * + * @return The material. + */ + public Material getMaterial() { + return this.material; + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/blocks/provider/BlockProvider.java b/eco-api/src/main/java/com/willfp/eco/core/blocks/provider/BlockProvider.java new file mode 100644 index 000000000..b378a89b2 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/blocks/provider/BlockProvider.java @@ -0,0 +1,49 @@ +package com.willfp.eco.core.blocks.provider; + +import com.willfp.eco.core.blocks.TestableBlock; +import com.willfp.eco.core.registry.Registry; +import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Block providers are call-site registrations for blocks. In other words, + * they only register their blocks when a request is made. This is marginally + * slower, however it is required for certain plugins, and fixes bugs related to + * loading orders. + * + * @see TestableBlock + */ +public abstract class BlockProvider { + /** + * The namespace. + */ + private final String namespace; + + /** + * Create a new BlockProvider for a specific namespace. + * + * @param namespace The namespace. + */ + protected BlockProvider(@NotNull final String namespace) { + this.namespace = namespace; + } + + /** + * Provide a TestableBlock for a given key. + * + * @param key The block ID. + * @return The TestableBlock, or null if not found. + */ + @Nullable + public abstract TestableBlock provideForKey(@NotNull String key); + + /** + * Get the namespace. + * + * @return The namespace. + */ + public String getNamespace() { + return this.namespace; + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/integrations/customblocks/CustomBlocksIntegration.java b/eco-api/src/main/java/com/willfp/eco/core/integrations/customblocks/CustomBlocksIntegration.java new file mode 100644 index 000000000..b46d38ea7 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/integrations/customblocks/CustomBlocksIntegration.java @@ -0,0 +1,24 @@ +package com.willfp.eco.core.integrations.customblocks; + +import com.willfp.eco.core.integrations.Integration; + +/** + * Wrapper class for custom block integrations. + */ +public interface CustomBlocksIntegration extends Integration { + /** + * Register all the custom block for a specific plugin into eco. + * + * @see com.willfp.eco.core.blocks.Blocks + */ + default void registerAllBlocks() { + // Override when needed. + } + + /** + * Register {@link com.willfp.eco.core.blocks.provider.BlockProvider}s. + */ + default void registerProvider() { + // Override when needed. + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/integrations/customblocks/CustomBlocksManager.java b/eco-api/src/main/java/com/willfp/eco/core/integrations/customblocks/CustomBlocksManager.java new file mode 100644 index 000000000..f796af7a6 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/integrations/customblocks/CustomBlocksManager.java @@ -0,0 +1,45 @@ +package com.willfp.eco.core.integrations.customblocks; + +import com.willfp.eco.core.integrations.IntegrationRegistry; +import org.jetbrains.annotations.NotNull; + +/** + * Class to handle custom block integrations. + */ +public final class CustomBlocksManager { + /** + * A set of all registered integrations. + */ + private static final IntegrationRegistry REGISTRY = new IntegrationRegistry<>(); + + /** + * Register a new integration. + * + * @param integration The integration to register. + */ + public static void register(@NotNull final CustomBlocksIntegration integration) { + REGISTRY.register(integration); + } + + /** + * Register all the custom block for a specific plugin into eco. + * + * @see com.willfp.eco.core.blocks.Blocks + */ + public static void registerAllBlocks() { + REGISTRY.forEachSafely(CustomBlocksIntegration::registerAllBlocks); + } + + /** + * Register all the custom blocks for a specific plugin into eco. + * + * @see com.willfp.eco.core.blocks.Blocks + */ + public static void registerProviders() { + REGISTRY.forEachSafely(CustomBlocksIntegration::registerProvider); + } + + private CustomBlocksManager() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } +} diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt index cc0fc8cfb..abb4f2e74 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/EcoSpigotPlugin.kt @@ -9,6 +9,7 @@ import com.willfp.eco.core.integrations.IntegrationLoader import com.willfp.eco.core.integrations.afk.AFKManager import com.willfp.eco.core.integrations.anticheat.AnticheatManager import com.willfp.eco.core.integrations.antigrief.AntigriefManager +import com.willfp.eco.core.integrations.customblocks.CustomBlocksManager import com.willfp.eco.core.integrations.customentities.CustomEntitiesManager import com.willfp.eco.core.integrations.customitems.CustomItemsManager import com.willfp.eco.core.integrations.economy.EconomyManager @@ -17,7 +18,6 @@ import com.willfp.eco.core.integrations.mcmmo.McmmoManager import com.willfp.eco.core.integrations.placeholder.PlaceholderManager import com.willfp.eco.core.integrations.shop.ShopManager import com.willfp.eco.core.items.Items -import com.willfp.eco.core.items.tag.VanillaItemTag import com.willfp.eco.core.packet.PacketListener import com.willfp.eco.core.particle.Particles import com.willfp.eco.core.price.Prices @@ -102,6 +102,7 @@ import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefRPGHorses import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefSuperiorSkyblock2 import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefTowny import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefWorldGuard +import com.willfp.eco.internal.spigot.integrations.customblocks.CustomBlocksOraxen import com.willfp.eco.internal.spigot.integrations.customentities.CustomEntitiesMythicMobs import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsCustomCrafting import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsDenizen @@ -408,6 +409,9 @@ abstract class EcoSpigotPlugin : EcoPlugin() { // Placeholder IntegrationLoader("PlaceholderAPI") { PlaceholderManager.addIntegration(PlaceholderIntegrationPAPI()) }, + // Custom Blocks + IntegrationLoader("Oraxen") { CustomBlocksManager.register(CustomBlocksOraxen(this)) }, + // Misc IntegrationLoader("mcMMO") { McmmoManager.register(McmmoIntegrationImpl()) }, IntegrationLoader("Multiverse-Inventories") { diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/integrations/customblocks/CustomBlocksOraxen.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/integrations/customblocks/CustomBlocksOraxen.kt new file mode 100644 index 000000000..97ddebb3e --- /dev/null +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/integrations/customblocks/CustomBlocksOraxen.kt @@ -0,0 +1,54 @@ +package com.willfp.eco.internal.spigot.integrations.customblocks + +import com.willfp.eco.core.EcoPlugin +import com.willfp.eco.core.blocks.Blocks +import com.willfp.eco.core.blocks.CustomBlock +import com.willfp.eco.core.blocks.TestableBlock +import com.willfp.eco.core.blocks.provider.BlockProvider +import com.willfp.eco.core.integrations.customblocks.CustomBlocksIntegration +import com.willfp.eco.util.namespacedKeyOf +import io.th0rgal.oraxen.api.OraxenBlocks +import io.th0rgal.oraxen.api.events.OraxenItemsLoadedEvent +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener + +class CustomBlocksOraxen( + private val plugin: EcoPlugin +) : CustomBlocksIntegration, Listener { + override fun registerProvider() { + plugin.eventManager.registerListener(this) + } + + override fun getPluginName(): String { + return "Oraxen" + } + + @EventHandler + @Suppress("UNUSED_PARAMETER") + fun onItemRegister(event: OraxenItemsLoadedEvent) { + Blocks.registerBlockProvider(OraxenProvider()) + } + + private class OraxenProvider : BlockProvider("oraxen") { + override fun provideForKey(key: String): TestableBlock? { + // The key + if (!OraxenBlocks.isOraxenBlock(key)) { + return null + } + + val namespacedKey = namespacedKeyOf("oraxen", key) + + return CustomBlock( + namespacedKey, + { block -> + // TODO: Implement this + TODO("Not yet implemented") + }, + { location -> + OraxenBlocks.place(key, location) + location.block + } + ) + } + } +}