diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 6d8de9243..1ebf13dc8 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -16,6 +16,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key CONCRETE_POWDER_BLOCK = Key.from("craftengine:concrete_powder_block"); public static final Key SUGARCANE_BLOCK = Key.from("craftengine:sugar_cane_block"); public static final Key CROP_BLOCK = Key.from("craftengine:crop_block"); + public static final Key GRASS_BLOCK = Key.from("craftengine:grass_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -30,5 +31,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(CONCRETE_POWDER_BLOCK, ConcretePowderBlockBehavior.FACTORY); register(SUGARCANE_BLOCK, SugarCaneBlockBehavior.FACTORY); register(CROP_BLOCK, CropBlockBehavior.FACTORY); + register(GRASS_BLOCK, GrassBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java new file mode 100644 index 000000000..3b0c868bc --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java @@ -0,0 +1,71 @@ +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; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.shared.block.BlockBehavior; +import org.bukkit.World; + +import java.util.Map; + +public class GrassBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + + public GrassBlockBehavior(CustomBlock block) { + super(block); + } + + @Override + public boolean isValidBoneMealTarget(Object thisBlock, Object[] args) { + return FastNMS.INSTANCE.method$GrassBlock$isValidBonemealTarget(args[0], args[1], args[2]); + } + + @Override + public boolean isBoneMealSuccess(Object thisBlock, Object[] args) throws Exception { + if (!VersionHelper.isOrAbove1_20_2()) return true; + 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 = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(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 + 1.5, z + 0.5, 20, 2, 0, 2); + } + return true; + } + + @Override + public void performBoneMeal(Object thisBlock, Object[] args) { + FastNMS.INSTANCE.method$GrassBlock$performBoneMeal(args[0], args[1], args[2], args[3], thisBlock); + } + + public static class Factory implements BlockBehaviorFactory { + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + return new GrassBlockBehavior(block); + } + } +} 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 index fc08fb7c6..6d60e9e44 100644 --- 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 @@ -2,6 +2,7 @@ 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.GrassBlockBehavior; import net.momirealms.craftengine.bukkit.block.behavior.SaplingBlockBehavior; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; @@ -43,6 +44,10 @@ public class BoneMealItemBehavior extends ItemBehavior { } } else if (state.behavior() instanceof SaplingBlockBehavior) { shouldHandle = true; + } else if (state.behavior() instanceof GrassBlockBehavior) { + if (block.getLocation().add(0, 1, 0).getBlock().isEmpty()) { + shouldHandle = true; + } } if (!shouldHandle) return InteractionResult.PASS; 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 index efe651614..3b44594e6 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ParticleUtils.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.bukkit.util; +import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Particle; public class ParticleUtils { @@ -10,7 +11,7 @@ public class ParticleUtils { } catch (IllegalArgumentException e) { return switch (particle) { case "REDSTONE" -> Particle.valueOf("DUST"); - case "VILLAGER_HAPPY" -> Particle.valueOf("HAPPY_VILLAGER"); + case "VILLAGER_HAPPY" -> Particle.valueOf(VersionHelper.isOrAbove1_20_5() ? "HAPPY_VILLAGER" : "VILLAGER_HAPPY"); default -> Particle.valueOf(particle); }; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java index 7d4d24a24..7ab1bed6b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java @@ -397,7 +397,7 @@ public class Reflections { ); public static final Class clazz$HolderLookup$Provider = BukkitReflectionUtils.findReobfOrMojmapClass( - "core.HolderLookup$b", + VersionHelper.isOrAbove1_20_5() ? "core.HolderLookup$a" : "core.HolderLookup$b", "core.HolderLookup$Provider" ); @@ -2076,6 +2076,12 @@ public class Reflections { ) ); + public static final Class clazz$PlacedFeature = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("world.level.levelgen.placement.PlacedFeature") + ) + ); + // 1.21+ public static final Class clazz$JukeboxSong = ReflectionUtils.getClazz( @@ -2092,6 +2098,7 @@ public class Reflections { public static final Object instance$BuiltInRegistries$ENTITY_TYPE; public static final Object instance$BuiltInRegistries$FLUID; public static final Object instance$BuiltInRegistries$RECIPE_TYPE; + public static final Object instance$BuiltInRegistries$PLACED_FEATURE; public static final Object instance$InternalRegistries$DIMENSION_TYPE; @Nullable // 1.21+ public static final Object instance$InternalRegistries$JUKEBOX_SONG; @@ -2108,6 +2115,7 @@ public class Reflections { public static final Object instance$Registries$RECIPE_TYPE; public static final Object instance$Registries$DIMENSION_TYPE; public static final Object instance$Registries$CONFIGURED_FEATURE; + public static final Object instance$Registries$PLACED_FEATURE; @Nullable // 1.21+ public static final Object instance$Registries$JUKEBOX_SONG; @@ -2128,6 +2136,7 @@ public class Reflections { Object registries$Fluid = null; Object registries$RecipeType = null; Object registries$ConfiguredFeature = null; + Object registries$PlacedFeature = null; Object registries$JukeboxSong = null; for (Field field : fields) { Type fieldType = field.getGenericType(); @@ -2166,6 +2175,8 @@ public class Reflections { registries$Fluid = field.get(null); } else if (VersionHelper.isOrAbove1_21() && type == clazz$JukeboxSong) { registries$JukeboxSong = field.get(null); + } else if (type == clazz$PlacedFeature) { + registries$PlacedFeature = field.get(null); } } } @@ -2184,6 +2195,7 @@ public class Reflections { instance$Registries$FLUID = requireNonNull(registries$Fluid); instance$Registries$RECIPE_TYPE = requireNonNull(registries$RecipeType); instance$Registries$CONFIGURED_FEATURE = requireNonNull(registries$ConfiguredFeature); + instance$Registries$PLACED_FEATURE = requireNonNull(registries$PlacedFeature); instance$Registries$JUKEBOX_SONG = registries$JukeboxSong; Object server = method$MinecraftServer$getServer.invoke(null); Object registries = field$MinecraftServer$registries.get(server); @@ -2199,6 +2211,7 @@ public class Reflections { instance$BuiltInRegistries$ENTITY_TYPE = method$RegistryAccess$registryOrThrow.invoke(instance$registryAccess, registries$EntityType); instance$BuiltInRegistries$FLUID = method$RegistryAccess$registryOrThrow.invoke(instance$registryAccess, registries$Fluid); instance$BuiltInRegistries$RECIPE_TYPE = method$RegistryAccess$registryOrThrow.invoke(instance$registryAccess, registries$RecipeType); + instance$BuiltInRegistries$PLACED_FEATURE = method$RegistryAccess$registryOrThrow.invoke(instance$registryAccess, registries$PlacedFeature); if (registries$JukeboxSong == null) instance$InternalRegistries$JUKEBOX_SONG = null; else instance$InternalRegistries$JUKEBOX_SONG = method$RegistryAccess$registryOrThrow.invoke(instance$registryAccess, registries$JukeboxSong); } catch (ReflectiveOperationException e) { @@ -3587,6 +3600,8 @@ public class Reflections { public static final Object instance$Blocks$FIRE; public static final Object instance$Blocks$SOUL_FIRE; public static final Object instance$Blocks$ICE; + public static final Object instance$Blocks$SHORT_GRASS; + public static final Object instance$Blocks$SHORT_GRASS$defaultState; static { try { @@ -3602,6 +3617,9 @@ public class Reflections { instance$Blocks$STONE$defaultState = method$Block$defaultBlockState.invoke(instance$Blocks$STONE); Object ice = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "ice"); instance$Blocks$ICE = method$Registry$get.invoke(instance$BuiltInRegistries$BLOCK, ice); + Object shortGrass = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", VersionHelper.isOrAbove1_20_3() ? "short_grass" : "grass"); + instance$Blocks$SHORT_GRASS = method$Registry$get.invoke(instance$BuiltInRegistries$BLOCK, shortGrass); + instance$Blocks$SHORT_GRASS$defaultState = method$Block$defaultBlockState.invoke(instance$Blocks$SHORT_GRASS); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } diff --git a/gradle.properties b/gradle.properties index 6bef76c12..edcf8da9f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.15 -nms_helper_version=0.62 +nms_helper_version=0.64 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23 amazon_awssdk_eventstream_version=1.0.1