Custom Mechanism
This page will use sugarcane as an example to roughly implement a mechanism similar to the vanilla sugarcane.
Firstly create two classes for both item and block
package net.momirealms.customcrops.api.example;
import net.momirealms.customcrops.api.core.item.AbstractCustomCropsItem;
import net.momirealms.customcrops.common.util.Key;
public class SugarCaneItem extends AbstractCustomCropsItem {
private static SugarCaneItem instance;
public SugarCaneItem() {
super(Key.key("customcrops", "sugarcane_item"));
}
public static SugarCaneItem instance() {
if (instance == null) {
instance = new SugarCaneItem();
}
return instance;
}
}
package net.momirealms.customcrops.api.example;
import net.momirealms.customcrops.api.core.block.AbstractCustomCropsBlock;
import net.momirealms.customcrops.common.util.Key;
public class SugarCaneBlock extends AbstractCustomCropsBlock {
private static SugarCaneBlock instance;
private SugarCaneBlock() {
super(Key.key("customcrops", "sugarcane_block"));
}
// This method is used to verify whether a block belongs to this mechanism
// The id would be the item ID from the custom item plugin for instance Oraxen
@Override
public boolean isBlockInstance(String id) {
return id.equals("customcrops:sugarcane_block_id");
}
public static SugarCaneBlock instance() {
if (instance == null) {
instance = new SugarCaneBlock();
}
return instance;
}
@Override
public NamedTextColor insightColor() {
return NamedTextColor.YELLOW;
}
}
Then register it in onEnable()
method
package net.momirealms.customcrops.api.example;
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
import net.momirealms.customcrops.api.core.Registries;
import net.momirealms.customcrops.api.core.RegistryAccess;
import net.momirealms.customcrops.api.event.CustomCropsReloadEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.List;
public class YourPlugin extends JavaPlugin implements Listener {
@Override
public void onEnable() {
RegistryAccess access = BukkitCustomCropsPlugin.getInstance().getRegistryAccess();
access.registerBlockMechanic(SugarCaneBlock.instance());
access.registerItemMechanic(SugarCaneItem.instance());
getServer().getPluginManager().registerEvents(this, this);
}
@EventHandler
public void onPluginReload(CustomCropsReloadEvent event) {
// ITEMS and BLOCKS are temporary mappings to associate item id with mechanism type
// For example, if you have multiple blocks with the same mechanism, such as red wool, green wool, and gray wool, you need to register them in this registry.
// This registry is cleared when the plugin is reloaded to apply changes to the configuration file
// NOTE: NEVER USE THE REGISTRIES ANNOTATED WITH @DONOTUSE
for (String sugarcaneItem : List.of("customcrops:sugarcane_item_id")) {
Registries.ITEMS.register(sugarcaneItem, SugarCaneItem.instance());
}
for (String sugarcaneBlock : List.of("customcrops:sugarcane_block_id")) {
Registries.BLOCKS.register(sugarcaneBlock, SugarCaneBlock.instance());
}
}
}
Then we can create some basic logics for instance placing the sugarcane block
package net.momirealms.customcrops.api.example;
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
import net.momirealms.customcrops.api.core.ExistenceForm;
import net.momirealms.customcrops.api.core.InteractionResult;
import net.momirealms.customcrops.api.core.item.AbstractCustomCropsItem;
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
import net.momirealms.customcrops.api.core.world.Pos3;
import net.momirealms.customcrops.api.core.wrapper.WrappedInteractEvent;
import net.momirealms.customcrops.api.util.LocationUtils;
import net.momirealms.customcrops.common.util.Key;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import java.util.Collection;
import java.util.List;
public class SugarCaneItem extends AbstractCustomCropsItem {
private static SugarCaneItem instance;
public SugarCaneItem() {
super(Key.key("customcrops", "sugarcane_item"));
}
public static SugarCaneItem instance() {
if (instance == null) {
instance = new SugarCaneItem();
}
return instance;
}
@Override
public InteractionResult interactAt(WrappedInteractEvent wrapped) {
// Sugarcane should be planted on blocks
if (wrapped.existenceForm() != ExistenceForm.BLOCK)
return InteractionResult.PASS;
// Sugarcane should be planted on the upper face
if (wrapped.clickedBlockFace() != BlockFace.UP)
return InteractionResult.PASS;
// Get the location of the clicked block
Location location = wrapped.location();
Block block = location.getBlock();
// should be planted on sand or dirt
if (block.getType() != Material.SAND && block.getType() != Material.RED_SAND && block.getType() != Material.DIRT) {
return InteractionResult.PASS;
}
// should be planted near water
outer: {
for (BlockFace face : List.of(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST)) {
if (block.getRelative(face).getType() == Material.WATER) {
break outer;
}
}
return InteractionResult.PASS;
}
// check if the place is suitable for planting
Location sugarcaneLocation = location.clone().add(0,1,0);
if (!suitableForPlant(sugarcaneLocation)) {
return InteractionResult.PASS;
}
// reduce the amount of item
wrapped.itemInHand().setAmount(wrapped.itemInHand().getAmount() - 1);
// Get the CustomCrops world and add block state
CustomCropsWorld<?> world = wrapped.world();
CustomCropsBlockState newBlockState = SugarCaneBlock.instance().createBlockState();
world.addBlockState(Pos3.from(sugarcaneLocation), newBlockState);
// place the block
BukkitCustomCropsPlugin.getInstance().getItemManager().placeBlock(sugarcaneLocation, "customcrops:sugarcane_block_id");
return InteractionResult.COMPLETE;
}
private boolean suitableForPlant(Location location) {
Block block = location.getBlock();
if (block.getType() != Material.AIR) return false;
Location center = LocationUtils.toBlockCenterLocation(location);
Collection<Entity> entities = center.getWorld().getNearbyEntities(center, 0.5,0.5,0.5);
entities.removeIf(entity -> (entity instanceof Player || entity instanceof Item));
return entities.isEmpty();
}
}
Now you should be able to plant the sugarcane (I used a pineapple as a substitute in the gif because I haven't prepared the model for sugarcane yet)
Then we can configure the grow/break logics for sugarcane
package net.momirealms.customcrops.api.example;
import net.momirealms.customcrops.api.BukkitCustomCropsPlugin;
import net.momirealms.customcrops.api.core.block.AbstractCustomCropsBlock;
import net.momirealms.customcrops.api.core.world.CustomCropsBlockState;
import net.momirealms.customcrops.api.core.world.CustomCropsWorld;
import net.momirealms.customcrops.api.core.world.Pos3;
import net.momirealms.customcrops.api.core.wrapper.WrappedBreakEvent;
import net.momirealms.customcrops.api.misc.NamedTextColor;
import net.momirealms.customcrops.api.util.LocationUtils;
import net.momirealms.customcrops.common.util.Key;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.Collection;
import java.util.Optional;
public class SugarCaneBlock extends AbstractCustomCropsBlock {
private static SugarCaneBlock instance;
private SugarCaneBlock() {
super(Key.key("customcrops", "sugarcane_block"));
}
@Override
public boolean isBlockInstance(String id) {
return id.equals("customcrops:sugarcane_block_id");
}
public static SugarCaneBlock instance() {
if (instance == null) {
instance = new SugarCaneBlock();
}
return instance;
}
@Override
public void randomTick(CustomCropsBlockState state, CustomCropsWorld<?> world, Pos3 location, boolean offlineTick) {
// firstly check if it's the top block
Pos3 upper = location.add(0,1,0);
Optional<CustomCropsBlockState> upperState = world.getBlockState(upper);
if (upperState.isPresent() && upperState.get().type() instanceof SugarCaneBlock) {
return;
}
// get the current height
int height = 1;
for (int i = 1; i < 4; i++) {
Pos3 under = location.add(0,-i,0);
Optional<CustomCropsBlockState> optionalState = world.getBlockState(under);
if (optionalState.isPresent() && optionalState.get().type() instanceof SugarCaneBlock) {
height++;
} else {
break;
}
}
if (height >= 4) return;
// check if there's enough space for growing
Location upperBukkitLocation = upper.toLocation(world.bukkitWorld());
// switch to the main thread
BukkitCustomCropsPlugin.getInstance().getScheduler().sync().run(() -> {
if (!suitableForGrow(upperBukkitLocation)) {
return;
}
// place the upper block
BukkitCustomCropsPlugin.getInstance().getItemManager().placeBlock(upperBukkitLocation, "customcrops:sugarcane_block_id");
world.addBlockState(upper, createBlockState());
}, upperBukkitLocation);
}
private boolean suitableForGrow(Location location) {
Block block = location.getBlock();
if (block.getType() != Material.AIR) return false;
Location center = LocationUtils.toBlockCenterLocation(location);
Collection<Entity> entities = center.getWorld().getNearbyEntities(center, 0.5,0.5,0.5);
entities.removeIf(entity -> (entity instanceof Player || entity instanceof Item));
return entities.isEmpty();
}
@Override
public void onBreak(WrappedBreakEvent event) {
Location location = event.location();
Pos3 pos3 = Pos3.from(location);
CustomCropsWorld<?> world = event.world();
// build the item
ItemStack itemToDrop = BukkitCustomCropsPlugin.getInstance().getItemManager().build(null, "customcrops:sugarcane_item_id");
// break the upper blocks
for (int i = 0; i < 4; i++) {
Pos3 upper = pos3.add(0, i,0);
Optional<CustomCropsBlockState> optionalState = world.getBlockState(upper);
if (optionalState.isPresent() && optionalState.get().type() instanceof SugarCaneBlock) {
world.removeBlockState(upper);
Location temp = upper.toLocation(world.bukkitWorld());
BukkitCustomCropsPlugin.getInstance().getItemManager().removeBlock(temp);
temp.getWorld().dropItemNaturally(temp, itemToDrop);
} else {
break;
}
}
}
@Override
public NamedTextColor insightColor() {
return NamedTextColor.YELLOW;
}
}
Last updated