From 348f004500e910c231d3d21a4b8e3fc132a62d1b Mon Sep 17 00:00:00 2001 From: halogly Date: Fri, 18 Jul 2025 13:42:49 +0800 Subject: [PATCH] Add support for Happy Ghast and harness interactions Introduces logic for handling Happy Ghast entity interactions and harness item tags, gated by Minecraft version 1.21.6. Refactors entity and block interaction checks to use new utility methods and dynamic entity sets, improving maintainability and future compatibility. --- .../item/listener/ItemEventListener.java | 17 ++++---- .../craftengine/bukkit/util/EntityUtils.java | 29 +++++++++++-- .../bukkit/util/InteractUtils.java | 43 ++++++++++++++----- .../craftengine/bukkit/util/ItemTags.java | 15 +++++++ 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java index b507b3b6f..8f2095acf 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java @@ -34,6 +34,8 @@ import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.ChiseledBookshelf; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Openable; import org.bukkit.entity.Entity; @@ -53,10 +55,7 @@ import org.bukkit.inventory.EnchantingInventory; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; public class ItemEventListener implements Listener { private final BukkitCraftEngine plugin; @@ -280,12 +279,12 @@ public class ItemEventListener implements Listener { .withParameter(DirectContextParameters.EVENT, dummy) ); CustomItem customItem = optionalCustomItem.get(); - if (!InteractUtils.isInteractable(player, blockData, hitResult, null)) { + if (!InteractUtils.isInteractable(player, blockData, hitResult, itemInHand) && !player.isSneaking()) { customItem.execute(context, EventTrigger.RIGHT_CLICK); - if (dummy.isCancelled()) { - event.setCancelled(true); - return; - } + } + if (dummy.isCancelled()) { + event.setCancelled(true); + return; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java index 30fd6fa4b..0dfe4ec0d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java @@ -2,14 +2,17 @@ package net.momirealms.craftengine.bukkit.util; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockPos; import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; +import org.bukkit.entity.*; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; import java.util.function.Consumer; @@ -34,4 +37,24 @@ public class EntityUtils { return LegacyEntityUtils.spawnEntity(world, loc, type, function); } } + + public static boolean isPiglinWithGoldIngot(Entity entity, Item item) { + return entity.getType() == EntityType.PIGLIN && + item != null && + item.vanillaId().equals(Key.of("minecraft:gold_ingot")); + } + + public static boolean isHappyGhastRideable(Entity entity) { + if (!VersionHelper.isOrAbove1_21_6() && + !entity.getType().name().equals("HAPPY_GHAST")) return false; + return entity instanceof LivingEntity livingEntity + && livingEntity.getEquipment() != null + && hasHarness(livingEntity.getEquipment()); + } + + public static boolean hasHarness(EntityEquipment equipment) { + ItemStack bodyItem = equipment.getItem(EquipmentSlot.BODY); + return ItemTags.ITEMS_HARNESSES != null && + ItemTags.ITEMS_HARNESSES.isTagged(bodyItem.getType()); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java index 281b1ed0f..54e1aba40 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/InteractUtils.java @@ -12,23 +12,25 @@ import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.util.Direction; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.QuadFunction; +import net.momirealms.craftengine.core.util.VersionHelper; import net.momirealms.craftengine.core.world.BlockHitResult; import net.momirealms.craftengine.core.world.BlockPos; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Bell; +import org.bukkit.block.data.type.ChiseledBookshelf; import org.bukkit.entity.*; import org.bukkit.entity.minecart.RideableMinecart; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; +import java.lang.reflect.Field; +import java.util.*; public class InteractUtils { private static final Map, BlockData, BlockHitResult, Boolean>> INTERACTIONS = new HashMap<>(); private static final Map, BlockData, BlockHitResult, Boolean>> WILL_CONSUME = new HashMap<>(); private static final Key NOTE_BLOCK_TOP_INSTRUMENTS = Key.of("minecraft:noteblock_top_instruments"); + private static final Set INTERACTABLE_ENTITIES = createInteractableEntities(); private InteractUtils() {} @@ -90,7 +92,15 @@ public class InteractUtils { item.recipeIngredientId(), item ))) != null; }); - registerInteraction(BlockKeys.CHISELED_BOOKSHELF, (player, item, blockState, result) -> true); + registerInteraction(BlockKeys.CHISELED_BOOKSHELF, (player, item, blockState, result) -> { + Direction direction = result.getDirection(); + if (player.isSneaking()) return false; + if (blockState instanceof ChiseledBookshelf chiseledBookshelf) { + Direction facing = DirectionUtils.toDirection(chiseledBookshelf.getFacing()); + return facing == direction; + } + return false; + }); registerInteraction(BlockKeys.DECORATED_POT, (player, item, blockState, result) -> true); registerInteraction(BlockKeys.HOPPER, (player, item, blockState, result) -> true); registerInteraction(BlockKeys.DISPENSER, (player, item, blockState, result) -> true); @@ -279,7 +289,9 @@ public class InteractUtils { result.getDirection() == Direction.UP && item.id().equals(ItemKeys.CACTUS)); } - private static final Set INTERACTABLE_ENTITIES = Set.of( + private static Set createInteractableEntities() { + Set set = EnumSet.noneOf(EntityType.class); + set.addAll(Set.of( EntityType.ALLAY, EntityType.HORSE, EntityType.ZOMBIE_HORSE, @@ -296,9 +308,17 @@ public class InteractUtils { EntityType.HOPPER_MINECART, EntityType.COMMAND_BLOCK_MINECART, EntityType.ITEM_FRAME, - EntityType.GLOW_ITEM_FRAME, - EntityType.HAPPY_GHAST - ); + EntityType.GLOW_ITEM_FRAME + )); + if (VersionHelper.isOrAbove1_21_6()) { + try { + Field happyGhastField = EntityType.class.getField("HAPPY_GHAST"); + EntityType happyGhast = (EntityType) happyGhastField.get(null); + set.add(happyGhast); + } catch (NoSuchFieldException | IllegalAccessException ignored) {} + } + return Collections.unmodifiableSet(set); + } private static void registerInteraction(Key key, QuadFunction, BlockData, BlockHitResult, Boolean> function) { var previous = INTERACTIONS.put(key, function); @@ -325,9 +345,10 @@ public class InteractUtils { public static boolean isEntityInteractable(Player player, Entity entity, Item item) { boolean isSneaking = player.isSneaking(); - if (entity.getType() == EntityType.PIGLIN && - item != null && - item.vanillaId().equals(Key.of("minecraft:gold_ingot"))) { + if (EntityUtils.isPiglinWithGoldIngot(entity, item)) { + return true; + } + if (EntityUtils.isHappyGhastRideable(entity) && !isSneaking) { return true; } return switch (entity) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemTags.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemTags.java index 1f35e3e90..62ab60190 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemTags.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/ItemTags.java @@ -3,6 +3,11 @@ package net.momirealms.craftengine.bukkit.util; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries; import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.VersionHelper; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Tag; import java.util.HashMap; import java.util.Map; @@ -12,6 +17,7 @@ public class ItemTags { public static final Key AXES = Key.of("minecraft:axes"); public static final Key SWORDS = Key.of("minecraft:swords"); + public static final Tag ITEMS_HARNESSES = getHarnessTag(); private ItemTags() {} @@ -23,4 +29,13 @@ public class ItemTags { } return value; } + + public static Tag getHarnessTag() { + if (!VersionHelper.isOrAbove1_21_6()) return null; + try { + return Bukkit.getTag("items", NamespacedKey.minecraft("harnesses"), Material.class); + } catch (Exception e) { + return null; + } + } }