Added custom blocks API with initial oraxen integration

This commit is contained in:
Auxilor
2024-07-30 15:57:44 +01:00
parent c89edd05f5
commit 4a8935f06d
13 changed files with 689 additions and 1 deletions

View File

@@ -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<NamespacedKey, TestableBlock> REGISTRY = new ConcurrentHashMap<>();
/**
* All block providers.
*/
private static final Map<String, BlockProvider> 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.
* <p>
* You can look up a TestableBlock for any material or custom block,
* and it will return it.
* <p>
* 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<TestableBlock> getCustomBlocks() {
return new HashSet<>(REGISTRY.values());
}
private Blocks() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
}

View File

@@ -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<TestableBlock> {
/**
* The parser.
*/
private final Function<String[], @NotNull TestableBlock> parser;
/**
* Create new lookup handler.
*
* @param parser The parser.
*/
public BlocksLookupHandler(@NotNull final Function<String[], @NotNull TestableBlock> 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<TestableBlock> options) {
return new GroupedTestableBlocks(options);
}
}

View File

@@ -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.
*
* <ul>
* <li>The key to identify it</li>
* <li>The test to check if any block is this custom block</li>
* <li>The supplier to spawn the custom {@link Block}</li>
* </ul>
*/
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<Location, Block> 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<Location, Block> 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;
}
}

View File

@@ -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<Block> {
/**
* 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);
}

View File

@@ -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();
}
}

View File

@@ -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<TestableBlock> children;
/**
* Create a new group of testable blocks.
*
* @param children The children.
*/
public GroupedTestableBlocks(@NotNull final Collection<TestableBlock> 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<TestableBlock> getChildren() {
return new ArrayList<>(children);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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.
}
}

View File

@@ -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<CustomBlocksIntegration> 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");
}
}

View File

@@ -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") {

View File

@@ -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
}
)
}
}
}