From ae71a1a09951cfd96665107928c3827fcdcbc61d Mon Sep 17 00:00:00 2001 From: shinoharatsuru Date: Mon, 27 Jul 2026 02:51:02 +0800 Subject: [PATCH] Fix vanilla bees in sublevels Makes vanilla bee flower search, hive search, and hive entry checks use Sable-aware sublevel distance logic. Also stops pollination at night so bees can return to hives/nests properly. Tested successfully in-game on NeoForge. --- .../entity/bee/BeeEnterHiveGoalMixin.java | 23 ++++ .../entity/bee/BeeLocateHiveGoalMixin.java | 25 ++++ .../sable/mixin/entity/bee/BeeMixin.java | 17 +++ .../entity/bee/BeePollinateGoalMixin.java | 46 +++++++ .../entity/bee/BeeSableHooks.java | 114 ++++++++++++++++++ common/src/main/resources/sable.mixins.json | 4 + 6 files changed, 229 insertions(+) create mode 100644 common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeEnterHiveGoalMixin.java create mode 100644 common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeLocateHiveGoalMixin.java create mode 100644 common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeMixin.java create mode 100644 common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeePollinateGoalMixin.java create mode 100644 common/src/main/java/dev/ryanhcode/sable/mixinhelpers/entity/bee/BeeSableHooks.java diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeEnterHiveGoalMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeEnterHiveGoalMixin.java new file mode 100644 index 00000000..774cbee8 --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeEnterHiveGoalMixin.java @@ -0,0 +1,23 @@ +package dev.ryanhcode.sable.mixin.entity.bee; + +import dev.ryanhcode.sable.mixinhelpers.entity.bee.BeeSableHooks; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Position; +import net.minecraft.world.entity.animal.Bee; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(targets = "net.minecraft.world.entity.animal.Bee$BeeEnterHiveGoal") +public abstract class BeeEnterHiveGoalMixin { + @Shadow + @Final + private Bee this$0; + + @Redirect(method = "canBeeUse", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z")) + private boolean sable$canEnterHive(final BlockPos hivePos, final Position position, final double distance) { + return BeeSableHooks.closerThan(this.this$0, hivePos, distance); + } +} diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeLocateHiveGoalMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeLocateHiveGoalMixin.java new file mode 100644 index 00000000..06f43768 --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeLocateHiveGoalMixin.java @@ -0,0 +1,25 @@ +package dev.ryanhcode.sable.mixin.entity.bee; + +import dev.ryanhcode.sable.mixinhelpers.entity.bee.BeeSableHooks; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.animal.Bee; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.List; + +@Mixin(targets = "net.minecraft.world.entity.animal.Bee$BeeLocateHiveGoal") +public abstract class BeeLocateHiveGoalMixin { + @Shadow + @Final + private Bee this$0; + + @Inject(method = "findNearbyHivesWithSpace", at = @At("HEAD"), cancellable = true) + private void sable$findNearbyHivesWithSpace(final CallbackInfoReturnable> cir) { + cir.setReturnValue(BeeSableHooks.findNearbyHivesWithSpace(this.this$0)); + } +} diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeMixin.java new file mode 100644 index 00000000..efac39dd --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeeMixin.java @@ -0,0 +1,17 @@ +package dev.ryanhcode.sable.mixin.entity.bee; + +import dev.ryanhcode.sable.mixinhelpers.entity.bee.BeeSableHooks; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.animal.Bee; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Bee.class) +public abstract class BeeMixin { + @Inject(method = "closerThan", at = @At("HEAD"), cancellable = true) + private void sable$closerThan(final BlockPos pos, final int distance, final CallbackInfoReturnable cir) { + cir.setReturnValue(BeeSableHooks.closerThan((Bee) (Object) this, pos, distance)); + } +} diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeePollinateGoalMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeePollinateGoalMixin.java new file mode 100644 index 00000000..fa7bb2b6 --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/bee/BeePollinateGoalMixin.java @@ -0,0 +1,46 @@ +package dev.ryanhcode.sable.mixin.entity.bee; + +import dev.ryanhcode.sable.mixinhelpers.entity.bee.BeeSableHooks; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.world.entity.animal.Bee; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Optional; +import java.util.function.Predicate; + +@Mixin(targets = "net.minecraft.world.entity.animal.Bee$BeePollinateGoal") +public abstract class BeePollinateGoalMixin { + @Shadow + @Final + private Bee this$0; + + @Shadow + @Final + private Predicate VALID_POLLINATION_BLOCKS; + + @Inject(method = "findNearbyFlower", at = @At("HEAD"), cancellable = true) + private void sable$findNearbyFlower(final CallbackInfoReturnable> cir) { + cir.setReturnValue(BeeSableHooks.findNearestFlower(this.this$0, this.VALID_POLLINATION_BLOCKS, 5.0)); + } + + @Inject(method = "canBeeContinueToUse", at = @At("HEAD"), cancellable = true) + private void sable$stopPollinatingAtNight(final CallbackInfoReturnable cir) { + if (this.this$0.level().isNight()) { + cir.setReturnValue(false); + } + } + + @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/phys/Vec3;atBottomCenterOf(Lnet/minecraft/core/Vec3i;)Lnet/minecraft/world/phys/Vec3;")) + private Vec3 sable$projectFlowerHoverTarget(final Vec3i pos) { + return BeeSableHooks.projectHoverTarget(this.this$0, new BlockPos(pos.getX(), pos.getY(), pos.getZ())); + } +} diff --git a/common/src/main/java/dev/ryanhcode/sable/mixinhelpers/entity/bee/BeeSableHooks.java b/common/src/main/java/dev/ryanhcode/sable/mixinhelpers/entity/bee/BeeSableHooks.java new file mode 100644 index 00000000..0ddb5d55 --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixinhelpers/entity/bee/BeeSableHooks.java @@ -0,0 +1,114 @@ +package dev.ryanhcode.sable.mixinhelpers.entity.bee; + +import dev.ryanhcode.sable.Sable; +import dev.ryanhcode.sable.companion.SubLevelAccess; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Position; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.PoiTypeTags; +import net.minecraft.world.entity.animal.Bee; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BeehiveBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.entity.ai.village.poi.PoiManager; +import net.minecraft.world.entity.ai.village.poi.PoiRecord; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.phys.Vec3; + +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public final class BeeSableHooks { + private BeeSableHooks() { + } + + public static boolean closerThan(final Bee bee, final BlockPos pos, final int distance) { + return closerThan(bee, pos, (double) distance); + } + + public static boolean closerThan(final Bee bee, final BlockPos pos, final double distance) { + final double maxDistanceSquared = distance * distance; + return Sable.HELPER.distanceSquaredWithSubLevels(bee.level(), pos.getCenter(), bee.position()) < maxDistanceSquared; + } + + public static List findNearbyHivesWithSpace(final Bee bee) { + final Level level = bee.level(); + if (!(level instanceof ServerLevel serverLevel)) { + return List.of(); + } + + final SubLevelAccess trackingSubLevel = Sable.HELPER.getTrackingOrVehicleSubLevel(bee); + final Position origin = getSearchOrigin(bee, trackingSubLevel); + final List hives = Sable.HELPER.runIncludingSubLevels(level, origin, true, trackingSubLevel, + (subLevel, candidateOrigin) -> findNearbyHivesWithSpace(serverLevel, bee, candidateOrigin)); + return hives == null ? List.of() : hives; + } + + public static Optional findNearestFlower(final Bee bee, final Predicate predicate, final double range) { + final SubLevelAccess trackingSubLevel = Sable.HELPER.getTrackingOrVehicleSubLevel(bee); + final Position origin = getSearchOrigin(bee, trackingSubLevel); + final Optional flower = Sable.HELPER.runIncludingSubLevels(bee.level(), origin, true, trackingSubLevel, + (subLevel, candidateOrigin) -> findNearestBlock(bee, predicate, range, candidateOrigin)); + return flower == null ? Optional.empty() : flower; + } + + public static Vec3 projectHoverTarget(final Bee bee, final BlockPos flowerPos) { + return Sable.HELPER.projectOutOfSubLevel(bee.level(), Vec3.atBottomCenterOf(flowerPos)); + } + + private static Position getSearchOrigin(final Bee bee, final SubLevelAccess trackingSubLevel) { + if (trackingSubLevel == null) { + return bee.position(); + } + + return trackingSubLevel.logicalPose().transformPositionInverse(bee.position()); + } + + private static List findNearbyHivesWithSpace(final ServerLevel level, final Bee bee, final BlockPos origin) { + final PoiManager poiManager = level.getPoiManager(); + final Stream records = poiManager.getInRange(record -> record.is(PoiTypeTags.BEE_HOME), origin, 20, PoiManager.Occupancy.ANY); + final List hives = records.map(PoiRecord::getPos) + .filter(pos -> hasHiveSpace(level, pos)) + .sorted(Comparator.comparingDouble(pos -> Sable.HELPER.distanceSquaredWithSubLevels(bee.level(), pos.getCenter(), bee.position()))) + .collect(Collectors.toList()); + return hives.isEmpty() ? null : hives; + } + + private static boolean hasHiveSpace(final Level level, final BlockPos pos) { + if (!level.getBlockState(pos).is(BlockTags.BEEHIVES)) { + return false; + } + + final BlockEntity blockEntity = level.getBlockEntity(pos); + return blockEntity instanceof BeehiveBlockEntity beehive && !beehive.isFull(); + } + + private static Optional findNearestBlock(final Bee bee, final Predicate predicate, final double range, final BlockPos origin) { + final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); + + for (int y = 0; (double) y <= range; y = y > 0 ? -y : 1 - y) { + for (int radius = 0; (double) radius < range; radius++) { + for (int x = 0; x <= radius; x = x > 0 ? -x : 1 - x) { + for (int z = x < radius && x > -radius ? radius : 0; z <= radius; z = z > 0 ? -z : 1 - z) { + mutablePos.setWithOffset(origin, x, y - 1, z); + if (isWithinSableAwareRange(bee.level(), bee.position(), mutablePos.getCenter(), range) + && predicate.test(bee.level().getBlockState(mutablePos))) { + return Optional.of(mutablePos.immutable()); + } + } + } + } + } + + return Optional.empty(); + } + + private static boolean isWithinSableAwareRange(final Level level, final Position origin, final Position candidate, final double range) { + return Sable.HELPER.distanceSquaredWithSubLevels(level, origin, candidate) < range * range; + } +} diff --git a/common/src/main/resources/sable.mixins.json b/common/src/main/resources/sable.mixins.json index a2d77709..df92b9d2 100644 --- a/common/src/main/resources/sable.mixins.json +++ b/common/src/main/resources/sable.mixins.json @@ -126,6 +126,10 @@ "death_message.EntityMixin", "enchanting_table.EnchantingTableBlockEntityMixin", "entity.arrows_hit_blocks.AbstractArrowMixin", + "entity.bee.BeeEnterHiveGoalMixin", + "entity.bee.BeeLocateHiveGoalMixin", + "entity.bee.BeeMixin", + "entity.bee.BeePollinateGoalMixin", "entity.entities_in_blocks.EntityMixin", "entity.entities_stick_sublevels.EntityMixin", "entity.entities_stick_sublevels.LivingEntityMixin",