diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java index 5fcaaba2e..9e3d8080c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/CropBlockBehavior.java @@ -1,7 +1,9 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.ParticleUtils; import net.momirealms.craftengine.bukkit.util.Reflections; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; @@ -13,6 +15,7 @@ import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.RandomUtils; import net.momirealms.craftengine.core.util.Tuple; import net.momirealms.craftengine.shared.block.BlockBehavior; +import org.bukkit.World; import java.lang.reflect.InvocationTargetException; import java.util.List; @@ -38,6 +41,10 @@ public class CropBlockBehavior extends BushBlockBehavior { return state.get(ageProperty); } + public boolean isMaxAge(ImmutableBlockState state) { + return state.get(ageProperty) == ageProperty.max; + } + private static int getRawBrightness(Object level, Object pos) throws InvocationTargetException, IllegalAccessException { return (int) Reflections.method$BlockAndTintGetter$getRawBrightness.invoke(level, pos, 0); } @@ -93,12 +100,31 @@ public class CropBlockBehavior extends BushBlockBehavior { if (immutableBlockState == null || immutableBlockState.isEmpty()) { return; } + boolean sendParticles = false; + Object visualState = immutableBlockState.vanillaBlockState().handle(); + Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState); + if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { + boolean is = (boolean) Reflections.method$BonemealableBlock$isValidBonemealTarget.invoke(visualStateBlock, level, pos, visualState); + if (!is) { + sendParticles = true; + } + } else { + sendParticles = true; + } + int i = this.getAge(immutableBlockState) + RandomUtils.generateRandomInt(2, 5); int maxAge = this.ageProperty.max; if (i > maxAge) { i = maxAge; } Reflections.method$Level$setBlock.invoke(level, pos, immutableBlockState.with(this.ageProperty, i).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags()); + if (sendParticles) { + World world = FastNMS.INSTANCE.method$Level$getCraftWorld(level); + int x = FastNMS.INSTANCE.field$Vec3i$x(pos); + int y = FastNMS.INSTANCE.field$Vec3i$y(pos); + int z = FastNMS.INSTANCE.field$Vec3i$z(pos); + world.spawnParticle(ParticleUtils.getParticle("HAPPY_VILLAGER"), x + 0.5, y + 0.5, z + 0.5, 12, 0.2, 0.2, 0.2); + } } public static class Factory implements BlockBehaviorFactory { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java index 7fb110cac..93206d75d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java @@ -3,10 +3,7 @@ package net.momirealms.craftengine.bukkit.block.behavior; import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; -import net.momirealms.craftengine.bukkit.util.BlockStateUtils; -import net.momirealms.craftengine.bukkit.util.FeatureUtils; -import net.momirealms.craftengine.bukkit.util.LocationUtils; -import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.bukkit.util.*; import net.momirealms.craftengine.core.block.CustomBlock; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.UpdateOption; @@ -98,8 +95,34 @@ public class SaplingBlockBehavior extends BushBlockBehavior { } @Override - public boolean isBoneMealSuccess(Object thisBlock, Object[] args) { - return RandomUtils.generateRandomDouble(0d, 1d) < this.boneMealSuccessChance; + public boolean isBoneMealSuccess(Object thisBlock, Object[] args) throws Exception { + boolean success = RandomUtils.generateRandomDouble(0d, 1d) < this.boneMealSuccessChance; + Object level = args[0]; + Object blockPos = args[2]; + Object blockState = args[3]; + ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState)); + if (immutableBlockState == null || immutableBlockState.isEmpty()) { + return false; + } + boolean sendParticles = false; + Object visualState = immutableBlockState.vanillaBlockState().handle(); + Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState); + if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { + boolean is = (boolean) Reflections.method$BonemealableBlock$isValidBonemealTarget.invoke(visualStateBlock, level, blockPos, visualState); + if (!is) { + sendParticles = true; + } + } else { + sendParticles = true; + } + if (sendParticles) { + World world = FastNMS.INSTANCE.method$Level$getCraftWorld(level); + int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos); + int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos); + int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos); + world.spawnParticle(ParticleUtils.getParticle("HAPPY_VILLAGER"), x + 0.5, y + 0.5, z + 0.5, 12, 0.2, 0.2, 0.2); + } + return success; } @Override diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java index 0f965dbc8..bba404771 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.bukkit.item; import net.momirealms.craftengine.bukkit.compatibility.item.NeigeItemsProvider; import net.momirealms.craftengine.bukkit.item.behavior.AxeItemBehavior; +import net.momirealms.craftengine.bukkit.item.behavior.BoneMealItemBehavior; import net.momirealms.craftengine.bukkit.item.behavior.BucketItemBehavior; import net.momirealms.craftengine.bukkit.item.behavior.WaterBucketItemBehavior; import net.momirealms.craftengine.bukkit.item.factory.BukkitItemFactory; @@ -50,6 +51,7 @@ public class BukkitItemManager extends AbstractItemManager { registerVanillaItemExtraBehavior(AxeItemBehavior.INSTANCE, ItemKeys.AXES); registerVanillaItemExtraBehavior(WaterBucketItemBehavior.INSTANCE, ItemKeys.WATER_BUCKETS); registerVanillaItemExtraBehavior(BucketItemBehavior.INSTANCE, ItemKeys.BUCKET); + registerVanillaItemExtraBehavior(BoneMealItemBehavior.INSTANCE, ItemKeys.BONE_MEAL); } private static BukkitItemManager instance; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java new file mode 100644 index 000000000..4a71f53bf --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BoneMealItemBehavior.java @@ -0,0 +1,73 @@ +package net.momirealms.craftengine.bukkit.item.behavior; + +import net.momirealms.craftengine.bukkit.block.BukkitBlockManager; +import net.momirealms.craftengine.bukkit.block.behavior.CropBlockBehavior; +import net.momirealms.craftengine.bukkit.block.behavior.SaplingBlockBehavior; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +import net.momirealms.craftengine.bukkit.util.Reflections; +import net.momirealms.craftengine.bukkit.world.BukkitWorldBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.behavior.ItemBehavior; +import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.pack.Pack; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.Key; +import org.bukkit.block.Block; + +import java.nio.file.Path; +import java.util.Map; + +public class BoneMealItemBehavior extends ItemBehavior { + public static final Factory FACTORY = new Factory(); + public static final BoneMealItemBehavior INSTANCE = new BoneMealItemBehavior(); + + @Override + public InteractionResult useOnBlock(UseOnContext context) { + BukkitWorldBlock clicked = (BukkitWorldBlock) context.getLevel().getBlockAt(context.getClickedPos()); + Block block = clicked.block(); + ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockDataToId(block.getBlockData())); + if (state == null || state.isEmpty()) return InteractionResult.PASS; + + boolean shouldHandle =false; + if (state.behavior() instanceof CropBlockBehavior blockBehavior) { + if (!blockBehavior.isMaxAge(state)) { + shouldHandle = true; + } + } else if (state.behavior() instanceof SaplingBlockBehavior) { + shouldHandle = true; + } + + if (!shouldHandle) return InteractionResult.PASS; + + boolean sendSwing = false; + try { + Object visualState = state.vanillaBlockState().handle(); + Object visualStateBlock = Reflections.method$BlockStateBase$getBlock.invoke(visualState); + if (Reflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { + boolean is = (boolean) Reflections.method$BonemealableBlock$isValidBonemealTarget.invoke(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); + if (!is) { + sendSwing = true; + } + } else { + sendSwing = true; + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to check visual state bone meal state", e); + } + if (sendSwing) { + context.getPlayer().swingHand(context.getHand()); + } + return InteractionResult.SUCCESS; + } + + public static class Factory implements ItemBehaviorFactory { + + @Override + public ItemBehavior create(Pack pack, Path path, Key id, Map arguments) { + return INSTANCE; + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java index 40bed4810..ed3e35737 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/behavior/BukkitItemBehaviors.java @@ -11,6 +11,7 @@ public class BukkitItemBehaviors extends ItemBehaviors { public static final Key AXE_ITEM = Key.from("craftengine:axe_item"); public static final Key WATER_BUCKET_ITEM = Key.from("craftengine:water_bucket_item"); public static final Key BUCKET_ITEM = Key.from("craftengine:bucket_item"); + public static final Key BONE_MEAL_ITEM = Key.from("craftengine:bone_meal_item"); public static void init() { register(EMPTY, EmptyItemBehavior.FACTORY); @@ -20,5 +21,6 @@ public class BukkitItemBehaviors extends ItemBehaviors { register(AXE_ITEM, AxeItemBehavior.FACTORY); register(WATER_BUCKET_ITEM, WaterBucketItemBehavior.FACTORY); register(BUCKET_ITEM, BucketItemBehavior.FACTORY); + register(BONE_MEAL_ITEM, BoneMealItemBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java new file mode 100644 index 000000000..efe651614 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java @@ -0,0 +1,18 @@ +package net.momirealms.craftengine.bukkit.util; + +import org.bukkit.Particle; + +public class ParticleUtils { + + public static Particle getParticle(String particle) { + try { + return Particle.valueOf(particle); + } catch (IllegalArgumentException e) { + return switch (particle) { + case "REDSTONE" -> Particle.valueOf("DUST"); + case "VILLAGER_HAPPY" -> Particle.valueOf("HAPPY_VILLAGER"); + default -> Particle.valueOf(particle); + }; + } + } +}