diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/AbstractCustomEventListener.java b/api/src/main/java/net/momirealms/customcrops/api/core/AbstractCustomEventListener.java index 58f57d2..dcf5e14 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/core/AbstractCustomEventListener.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/AbstractCustomEventListener.java @@ -17,20 +17,40 @@ package net.momirealms.customcrops.api.core; +import net.momirealms.customcrops.api.BukkitCustomCropsPlugin; +import net.momirealms.customcrops.api.action.ActionManager; +import net.momirealms.customcrops.api.context.Context; +import net.momirealms.customcrops.api.context.ContextKeys; +import net.momirealms.customcrops.api.core.block.*; +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.event.BoneMealDispenseEvent; +import net.momirealms.customcrops.api.util.EventUtils; +import net.momirealms.customcrops.api.util.LocationUtils; import net.momirealms.customcrops.common.helper.VersionHelper; +import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.block.Dispenser; +import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.*; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.ItemSpawnEvent; import org.bukkit.event.player.PlayerInteractAtEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; -import java.util.HashSet; -import java.util.List; +import java.util.*; public abstract class AbstractCustomEventListener implements Listener { @@ -135,4 +155,220 @@ public abstract class AbstractCustomEventListener implements Listener { event ); } + + @EventHandler (ignoreCancelled = true) + public void onItemSpawn(ItemSpawnEvent event) { + Item item = event.getEntity(); + ItemStack itemStack = item.getItemStack(); + String itemID = this.itemManager.id(itemStack); + + if (Registries.STAGE_TO_CROP_UNSAFE.containsKey(itemID)) { + event.setCancelled(true); + return; + } + + SprinklerConfig sprinkler = Registries.ITEM_TO_SPRINKLER.get(itemID); + if (sprinkler != null) { + String twoD = sprinkler.twoDItem(); + if (twoD != null) { + if (!twoD.equals(itemID)) { + ItemStack newItem = this.itemManager.build(null, twoD); + if (newItem != null && newItem.getType() != Material.AIR) { + newItem.setAmount(itemStack.getAmount()); + item.setItemStack(newItem); + } + return; + } + } + } + + PotConfig pot = Registries.ITEM_TO_POT.get(itemID); + if (pot != null) { + ItemStack newItem = this.itemManager.build(null, pot.getPotAppearance(false, null)); + if (newItem != null && newItem.getType() != Material.AIR) { + newItem.setAmount(itemStack.getAmount()); + item.setItemStack(newItem); + } + return; + } + } + + @EventHandler (ignoreCancelled = true) + public void onBlockChange(BlockFadeEvent event) { + Block block = event.getBlock(); + if (block.getType() == Material.FARMLAND) { + Pos3 above = Pos3.from(block.getLocation()).add(0,1,0); + BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(block.getWorld()) + .flatMap(world -> world.getBlockState(above)).ifPresent(blockState -> { + if (blockState.type() instanceof CropBlock) { + event.setCancelled(true); + } + }); + } + } + + @EventHandler (ignoreCancelled = true) + public void onTrampling(EntityChangeBlockEvent event) { + Block block = event.getBlock(); + if (block.getType() == Material.FARMLAND && event.getTo() == Material.DIRT) { + if (ConfigManager.preventTrampling()) { + event.setCancelled(true); + return; + } + this.itemManager.handleEntityTrample(event.getEntity(), block.getLocation(), "FARMLAND", event); + } + } + + @EventHandler (ignoreCancelled = true) + public void onMoistureChange(MoistureChangeEvent event) { + if (ConfigManager.disableMoistureMechanic()) + event.setCancelled(true); + } + + @EventHandler (ignoreCancelled = true) + public void onPistonExtend(BlockPistonExtendEvent event) { + Optional> world = BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(event.getBlock().getWorld()); + if (world.isEmpty()){ + return; + } + CustomCropsWorld w = world.get(); + for (Block block : event.getBlocks()) { + if (w.getBlockState(Pos3.from(block.getLocation())).isPresent()) { + event.setCancelled(true); + return; + } + } + } + + @EventHandler (ignoreCancelled = true) + public void onPistonRetract(BlockPistonRetractEvent event) { + Optional> world = BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(event.getBlock().getWorld()); + if (world.isEmpty()){ + return; + } + CustomCropsWorld w = world.get(); + for (Block block : event.getBlocks()) { + if (w.getBlockState(Pos3.from(block.getLocation())).isPresent()) { + event.setCancelled(true); + return; + } + } + } + + @EventHandler (ignoreCancelled = true) + public void onItemDamage(PlayerItemDamageEvent event) { + ItemStack itemStack = event.getItem(); + String itemID = this.itemManager.id(itemStack); + if (Registries.ITEM_TO_WATERING_CAN.containsKey(itemID)) { + event.setCancelled(true); + } + } + + @EventHandler (ignoreCancelled = true, priority = EventPriority.HIGHEST) + public void onExplosion(EntityExplodeEvent event) { + Entity entity = event.getEntity(); + for (Block block : event.blockList()) { + this.itemManager.handleEntityExplode(entity, block.getLocation(), this.itemManager.blockID(block), event); + if (event.isCancelled()) { + return; + } + } + } + + @EventHandler (ignoreCancelled = true, priority = EventPriority.HIGHEST) + public void onExplosion(BlockExplodeEvent event) { + Block exploder = event.getBlock(); + for (Block block : event.blockList()) { + this.itemManager.handleBlockExplode(exploder, block.getLocation(), this.itemManager.blockID(block), event); + if (event.isCancelled()) { + return; + } + } + } + + @EventHandler (ignoreCancelled = true) + public void onDispenser(BlockDispenseEvent event) { + Block block = event.getBlock(); + if (!(block.getBlockData() instanceof org.bukkit.block.data.type.Dispenser directional)) { + return; + } + Optional> optionalWorld = BukkitCustomCropsPlugin.getInstance().getWorldManager().getWorld(event.getBlock().getWorld()); + if (optionalWorld.isEmpty()){ + return; + } + CustomCropsWorld world = optionalWorld.get(); + Block relative = block.getRelative(directional.getFacing()); + Location location = relative.getLocation(); + Pos3 pos3 = Pos3.from(location); + world.getBlockState(pos3).ifPresent(state -> { + if (state.type() instanceof CropBlock cropBlock) { + ItemStack itemStack = event.getItem(); + String itemID = itemManager.id(itemStack); + CropConfig cropConfig = cropBlock.config(state); + int point = cropBlock.point(state); + if (point < cropConfig.maxPoints()) { + for (BoneMeal boneMeal : cropConfig.boneMeals()) { + if (boneMeal.isDispenserAllowed()) { + if (EventUtils.fireAndCheckCancel(new BoneMealDispenseEvent(block, itemStack, location, boneMeal, state))) { + event.setCancelled(true); + return; + } + if (block.getState() instanceof Dispenser dispenser) { + event.setCancelled(true); + Inventory inventory = dispenser.getInventory(); + for (ItemStack storage : inventory.getStorageContents()) { + if (storage == null) continue; + String id = itemManager.id(storage); + if (id.equals(itemID)) { + storage.setAmount(storage.getAmount() - 1); + Context context = Context.player(null); + context.arg(ContextKeys.LOCATION, location); + boneMeal.triggerActions(context); + + int afterPoints = Math.min(point + boneMeal.rollPoint(), cropConfig.maxPoints()); + cropBlock.point(state, afterPoints); + + String afterStage = null; + ExistenceForm afterForm = null; + int tempPoints = afterPoints; + while (tempPoints >= 0) { + Map.Entry afterEntry = cropConfig.getFloorStageEntry(tempPoints); + CropStageConfig after = afterEntry.getValue(); + if (after.stageID() != null) { + afterStage = after.stageID(); + afterForm = after.existenceForm(); + break; + } + tempPoints = after.point() - 1; + } + + Objects.requireNonNull(afterForm); + Objects.requireNonNull(afterStage); + + Context blockContext = Context.block(state); + blockContext.arg(ContextKeys.LOCATION, LocationUtils.toBlockLocation(location)); + for (int i = point + 1; i <= afterPoints; i++) { + CropStageConfig stage = cropConfig.stageByPoint(i); + if (stage != null) { + ActionManager.trigger(blockContext, stage.growActions()); + } + } + + // TODO if the block model chanegs + Location bukkitLocation = location.toLocation(world.bukkitWorld()); + FurnitureRotation rotation = BukkitCustomCropsPlugin.getInstance().getItemManager().remove(bukkitLocation, ExistenceForm.ANY); + if (rotation == FurnitureRotation.NONE && cropConfig.rotation()) { + rotation = FurnitureRotation.random(); + } + BukkitCustomCropsPlugin.getInstance().getItemManager().place(bukkitLocation, afterForm, afterStage, rotation); + } + } + } + return; + } + } + } + } + }); + } } diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/AbstractItemManager.java b/api/src/main/java/net/momirealms/customcrops/api/core/AbstractItemManager.java index b8f3a23..9ebf724 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/core/AbstractItemManager.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/AbstractItemManager.java @@ -20,6 +20,7 @@ package net.momirealms.customcrops.api.core; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.inventory.EquipmentSlot; @@ -61,6 +62,27 @@ public abstract class AbstractItemManager implements ItemManager { Cancellable event ); + public abstract void handleEntityTrample( + Entity entity, + Location location, + String brokenID, + Cancellable event + ); + + public abstract void handleEntityExplode( + Entity entity, + Location location, + String brokenID, + Cancellable event + ); + + public abstract void handleBlockExplode( + Block block, + Location location, + String brokenID, + Cancellable event + ); + public abstract void handlePlayerPlace( Player player, Location location, diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/ConfigManager.java b/api/src/main/java/net/momirealms/customcrops/api/core/ConfigManager.java index 7eec0d2..6e05e14 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/core/ConfigManager.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/ConfigManager.java @@ -61,7 +61,8 @@ public abstract class ConfigManager implements ConfigLoader, Reloadable { protected double[] defaultQualityRatio; - protected boolean hasNamespace; + protected boolean preventTrampling; + protected boolean disableMoistureMechanic; public ConfigManager(BukkitCustomCropsPlugin plugin) { this.plugin = plugin; @@ -144,8 +145,12 @@ public abstract class ConfigManager implements ConfigLoader, Reloadable { return instance.defaultQualityRatio; } - public static boolean hasNamespace() { - return instance.hasNamespace; + public static boolean preventTrampling() { + return instance.preventTrampling; + } + + public static boolean disableMoistureMechanic() { + return instance.disableMoistureMechanic; } @Override diff --git a/api/src/main/java/net/momirealms/customcrops/api/core/block/CrowAttack.java b/api/src/main/java/net/momirealms/customcrops/api/core/block/CrowAttack.java index 181391d..51d571c 100644 --- a/api/src/main/java/net/momirealms/customcrops/api/core/block/CrowAttack.java +++ b/api/src/main/java/net/momirealms/customcrops/api/core/block/CrowAttack.java @@ -36,7 +36,6 @@ public class CrowAttack { private SchedulerTask task; private final Location dynamicLocation; - private final Location cropLocation; private final Vector vectorDown; private final Vector vectorUp; private final Player[] viewers; @@ -52,14 +51,14 @@ public class CrowAttack { } } this.viewers = viewers.toArray(new Player[0]); - this.cropLocation = LocationUtils.toBlockCenterLocation(location).add(RandomUtils.generateRandomDouble(-0.25, 0.25), 0, RandomUtils.generateRandomDouble(-0.25, 0.25)); + Location landLocation = LocationUtils.toBlockCenterLocation(location).add(RandomUtils.generateRandomDouble(-0.25, 0.25), 0, RandomUtils.generateRandomDouble(-0.25, 0.25)); float yaw = RandomUtils.generateRandomInt(-180, 180); - this.cropLocation.setYaw(yaw); + landLocation.setYaw(yaw); this.flyModel = flyModel; this.standModel = standModel; - this.dynamicLocation = cropLocation.clone().add((10 * Math.sin((Math.PI * yaw) / 180)), 10, (- 10 * Math.cos((Math.PI * yaw) / 180))); + this.dynamicLocation = landLocation.clone().add((10 * Math.sin((Math.PI * yaw) / 180)), 10, (- 10 * Math.cos((Math.PI * yaw) / 180))); this.dynamicLocation.setYaw(yaw); - Location relative = cropLocation.clone().subtract(dynamicLocation); + Location relative = landLocation.clone().subtract(dynamicLocation); this.vectorDown = new Vector(relative.getX() / 100, -0.1, relative.getZ() / 100); this.vectorUp = new Vector(relative.getX() / 100, 0.1, relative.getZ() / 100); } diff --git a/api/src/main/java/net/momirealms/customcrops/api/event/BoneMealDispenseEvent.java b/api/src/main/java/net/momirealms/customcrops/api/event/BoneMealDispenseEvent.java new file mode 100644 index 0000000..9ea3a55 --- /dev/null +++ b/api/src/main/java/net/momirealms/customcrops/api/event/BoneMealDispenseEvent.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customcrops.api.event; + +import net.momirealms.customcrops.api.core.block.BoneMeal; +import net.momirealms.customcrops.api.core.world.CustomCropsBlockState; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * An event that triggered when the bone meal is dispensed + */ +public class BoneMealDispenseEvent extends Event implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + private boolean cancelled; + private final Location location; + private final BoneMeal boneMeal; + private final CustomCropsBlockState blockState; + private final ItemStack boneMealItem; + private final Block dispenser; + + public BoneMealDispenseEvent( + @NotNull Block dispenser, + @NotNull ItemStack boneMealItem, + @NotNull Location location, + @NotNull BoneMeal boneMeal, + @NotNull CustomCropsBlockState blockState + ) { + this.location = location; + this.blockState = blockState; + this.boneMeal = boneMeal; + this.boneMealItem = boneMealItem; + this.dispenser = dispenser; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @NotNull + public static HandlerList getHandlerList() { + return handlers; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return getHandlerList(); + } + + /** + * Get the crop location + * @return location + */ + @NotNull + public Location getLocation() { + return location; + } + + /** + * Get the item in player's hand + * If there's nothing in hand, it would return AIR + * @return item in hand + */ + @NotNull + public ItemStack getBoneMealItem() { + return boneMealItem; + } + + @NotNull + public CustomCropsBlockState getBlockState() { + return blockState; + } + + /** + * Get the bone meal's config + * + * @return bone meal config + */ + @NotNull + public BoneMeal getBoneMeal() { + return boneMeal; + } + + /** + * Get the dispenser block + * + * @return dispenser block + */ + @NotNull + public Block getDispenser() { + return dispenser; + } +} diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/config/BukkitConfigManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/config/BukkitConfigManager.java index b5ca93c..283277f 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/bukkit/config/BukkitConfigManager.java +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/config/BukkitConfigManager.java @@ -36,7 +36,6 @@ import net.momirealms.customcrops.api.core.block.PotConfig; import net.momirealms.customcrops.api.core.block.SprinklerConfig; import net.momirealms.customcrops.api.core.item.FertilizerConfig; import net.momirealms.customcrops.api.core.item.WateringCanConfig; -import net.momirealms.customcrops.api.util.PluginUtils; import net.momirealms.customcrops.common.helper.AdventureHelper; import net.momirealms.customcrops.common.locale.TranslationManager; import net.momirealms.customcrops.common.plugin.CustomCropsProperties; @@ -131,7 +130,8 @@ public class BukkitConfigManager extends ConfigManager { defaultQualityRatio = getQualityRatio(config.getString("mechanics.default-quality-ratio", "17/2/1")); - hasNamespace = PluginUtils.isEnabled("ItemsAdder"); + preventTrampling = config.getBoolean("mechanics.vanilla-farmland.prevent-trampling", false); + disableMoistureMechanic = config.getBoolean("mechanics.vanilla-farmland.disable-moisture-mechanic", false); } @Override diff --git a/plugin/src/main/java/net/momirealms/customcrops/bukkit/item/BukkitItemManager.java b/plugin/src/main/java/net/momirealms/customcrops/bukkit/item/BukkitItemManager.java index 83e7a39..90c6a94 100644 --- a/plugin/src/main/java/net/momirealms/customcrops/bukkit/item/BukkitItemManager.java +++ b/plugin/src/main/java/net/momirealms/customcrops/bukkit/item/BukkitItemManager.java @@ -449,6 +449,51 @@ public class BukkitItemManager extends AbstractItemManager { } } + @Override + public void handleEntityTrample(Entity entity, Location location, String brokenID, Cancellable event) { + Optional> optionalWorld = plugin.getWorldManager().getWorld(entity.getWorld()); + if (optionalWorld.isEmpty()) { + return; + } + + CustomCropsWorld world = optionalWorld.get(); + WrappedBreakEvent wrapped = new WrappedBreakEvent(entity, null, world, location, brokenID, null, null, BreakReason.TRAMPLE, event); + CustomCropsBlock customCropsBlock = Registries.BLOCKS.get(brokenID); + if (customCropsBlock != null) { + customCropsBlock.onBreak(wrapped); + } + } + + @Override + public void handleEntityExplode(Entity entity, Location location, String brokenID, Cancellable event) { + Optional> optionalWorld = plugin.getWorldManager().getWorld(entity.getWorld()); + if (optionalWorld.isEmpty()) { + return; + } + + CustomCropsWorld world = optionalWorld.get(); + WrappedBreakEvent wrapped = new WrappedBreakEvent(entity, null, world, location, brokenID, null, null, BreakReason.EXPLODE, event); + CustomCropsBlock customCropsBlock = Registries.BLOCKS.get(brokenID); + if (customCropsBlock != null) { + customCropsBlock.onBreak(wrapped); + } + } + + @Override + public void handleBlockExplode(Block block, Location location, String brokenID, Cancellable event) { + Optional> optionalWorld = plugin.getWorldManager().getWorld(block.getWorld()); + if (optionalWorld.isEmpty()) { + return; + } + + CustomCropsWorld world = optionalWorld.get(); + WrappedBreakEvent wrapped = new WrappedBreakEvent(null, block, world, location, brokenID, null, null, BreakReason.EXPLODE, event); + CustomCropsBlock customCropsBlock = Registries.BLOCKS.get(brokenID); + if (customCropsBlock != null) { + customCropsBlock.onBreak(wrapped); + } + } + @Override public void handlePlayerPlace(Player player, Location location, String placedID, EquipmentSlot hand, ItemStack itemInHand, Cancellable event) { Optional> optionalWorld = plugin.getWorldManager().getWorld(player.getWorld()); @@ -461,9 +506,18 @@ public class BukkitItemManager extends AbstractItemManager { Optional optionalState = world.getBlockState(pos3); if (optionalState.isPresent()) { CustomCropsBlockState customCropsBlockState = optionalState.get(); - String anyID = anyID(location); - System.out.println(anyID); - if (!customCropsBlockState.type().isBlockInstance(anyID)) { + String anyFurnitureID = furnitureID(location); + if (anyFurnitureID != null) { + if (!customCropsBlockState.type().isBlockInstance(anyFurnitureID)) { + world.removeBlockState(pos3); + plugin.debug("[" + location.getWorld().getName() + "] Removed inconsistent block data at " + pos3 + " which used to be " + customCropsBlockState); + } else { + event.setCancelled(true); + return; + } + } + String anyBlockID = blockID(location); + if (!customCropsBlockState.type().isBlockInstance(anyBlockID)) { world.removeBlockState(pos3); plugin.debug("[" + location.getWorld().getName() + "] Removed inconsistent block data at " + pos3 + " which used to be " + customCropsBlockState); } else {