diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/AcquirePoiMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/AcquirePoiMixin.java new file mode 100644 index 00000000..9d96ce7f --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/AcquirePoiMixin.java @@ -0,0 +1,29 @@ +package dev.ryanhcode.sable.mixin.entity.villager_poi; + +import dev.ryanhcode.sable.Sable; +import dev.ryanhcode.sable.sublevel.SubLevel; +import dev.ryanhcode.sable.util.SubLevelPoiUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.PathfinderMob; +import net.minecraft.world.entity.ai.behavior.AcquirePoi; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(AcquirePoi.class) +public class AcquirePoiMixin { + + /** + * When a villager is standing on a contraption, redirect the PoiManager scan center from the + * villager's global position to its plotyard position so it can find job sites inside the sublevel. + */ + @Redirect( + method = "*", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/PathfinderMob;blockPosition()Lnet/minecraft/core/BlockPos;") + ) + private static BlockPos sable$redirectScanCenter(final PathfinderMob mob) { + final SubLevel sl = Sable.HELPER.getTrackingSubLevel(mob); + if (sl == null) return mob.blockPosition(); + return SubLevelPoiUtil.toPlotyard(sl, mob.position()); + } +} diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/AssignProfessionFromJobSiteMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/AssignProfessionFromJobSiteMixin.java new file mode 100644 index 00000000..81ce3108 --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/AssignProfessionFromJobSiteMixin.java @@ -0,0 +1,32 @@ +package dev.ryanhcode.sable.mixin.entity.villager_poi; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import dev.ryanhcode.sable.sublevel.SubLevel; +import dev.ryanhcode.sable.util.SubLevelPoiUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Position; +import net.minecraft.world.entity.ai.behavior.AssignProfessionFromJobSite; +import net.minecraft.world.entity.npc.Villager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(AssignProfessionFromJobSite.class) +public class AssignProfessionFromJobSiteMixin { + + @WrapOperation( + method = "*", + at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z") + ) + private static boolean sable$fixProximityCheck( + final BlockPos potentialSitePos, final Position entityPos, final double dist, final Operation original, + @Local(argsOnly = true, ordinal = 0) final Villager villager + ) { + final SubLevel sl = SubLevelPoiUtil.getSubLevelForPos(villager.level(), potentialSitePos); + if (sl == null) return original.call(potentialSitePos, entityPos, dist); + return potentialSitePos.closerToCenterThan( + SubLevelPoiUtil.toPlotyard(sl, villager.position()).getCenter(), dist + ); + } +} diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/SetWalkTargetFromBlockMemoryMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/SetWalkTargetFromBlockMemoryMixin.java new file mode 100644 index 00000000..29db2e2c --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/SetWalkTargetFromBlockMemoryMixin.java @@ -0,0 +1,25 @@ +package dev.ryanhcode.sable.mixin.entity.villager_poi; + +import dev.ryanhcode.sable.Sable; +import dev.ryanhcode.sable.sublevel.SubLevel; +import dev.ryanhcode.sable.util.SubLevelPoiUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.ai.behavior.SetWalkTargetFromBlockMemory; +import net.minecraft.world.entity.npc.Villager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(SetWalkTargetFromBlockMemory.class) +public class SetWalkTargetFromBlockMemoryMixin { + + @Redirect( + method = "*", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/npc/Villager;blockPosition()Lnet/minecraft/core/BlockPos;") + ) + private static BlockPos sable$redirectBlockPos(final Villager villager) { + final SubLevel sl = Sable.HELPER.getTrackingSubLevel(villager); + if (sl == null) return villager.blockPosition(); + return SubLevelPoiUtil.toPlotyard(sl, villager.position()); + } +} diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/ValidateNearbyPoiMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/ValidateNearbyPoiMixin.java new file mode 100644 index 00000000..c7534fea --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/ValidateNearbyPoiMixin.java @@ -0,0 +1,40 @@ +package dev.ryanhcode.sable.mixin.entity.villager_poi; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import dev.ryanhcode.sable.Sable; +import dev.ryanhcode.sable.sublevel.SubLevel; +import dev.ryanhcode.sable.util.SubLevelPoiUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Position; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.behavior.ValidateNearbyPoi; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(ValidateNearbyPoi.class) +public class ValidateNearbyPoiMixin { + + @WrapOperation( + method = "*", + at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z") + ) + private static boolean sable$fixProximityCheck( + final BlockPos storedPos, final Position entityPos, final double dist, final Operation original, + @Local(argsOnly = true, ordinal = 0) final LivingEntity entity + ) { + final SubLevel sl = SubLevelPoiUtil.getSubLevelForPos(entity.level(), storedPos); + if (sl == null) return original.call(storedPos, entityPos, dist); + + if (Sable.HELPER.getTrackingSubLevel(entity) == sl) { + return storedPos.closerToCenterThan( + SubLevelPoiUtil.toPlotyard(sl, entity.position()).getCenter(), dist + ); + } + + final Vec3 globalJobSitePos = Sable.HELPER.projectOutOfSubLevel(entity.level(), storedPos.getCenter()); + return globalJobSitePos.closerThan(entity.position(), dist); + } +} diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/WorkAtPoiMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/WorkAtPoiMixin.java new file mode 100644 index 00000000..ee23141e --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/villager_poi/WorkAtPoiMixin.java @@ -0,0 +1,33 @@ +package dev.ryanhcode.sable.mixin.entity.villager_poi; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import dev.ryanhcode.sable.sublevel.SubLevel; +import dev.ryanhcode.sable.util.SubLevelPoiUtil; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Position; +import net.minecraft.world.entity.ai.behavior.WorkAtPoi; +import net.minecraft.world.entity.npc.Villager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(WorkAtPoi.class) +public class WorkAtPoiMixin { + + @WrapOperation( + method = {"checkExtraStartConditions(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/npc/Villager;)Z", + "canStillUse(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/npc/Villager;J)Z"}, + at = @At(value = "INVOKE", target = "Lnet/minecraft/core/BlockPos;closerToCenterThan(Lnet/minecraft/core/Position;D)Z") + ) + private boolean sable$fixProximityCheck( + final BlockPos jobSitePos, final Position entityPos, final double dist, final Operation original, + @Local(argsOnly = true, ordinal = 0) final Villager villager + ) { + final SubLevel sl = SubLevelPoiUtil.getSubLevelForPos(villager.level(), jobSitePos); + if (sl == null) return original.call(jobSitePos, entityPos, dist); + return jobSitePos.closerToCenterThan( + SubLevelPoiUtil.toPlotyard(sl, villager.position()).getCenter(), dist + ); + } +} diff --git a/common/src/main/java/dev/ryanhcode/sable/util/SubLevelPoiUtil.java b/common/src/main/java/dev/ryanhcode/sable/util/SubLevelPoiUtil.java new file mode 100644 index 00000000..b4644fcb --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/util/SubLevelPoiUtil.java @@ -0,0 +1,22 @@ +package dev.ryanhcode.sable.util; + +import dev.ryanhcode.sable.Sable; +import dev.ryanhcode.sable.sublevel.SubLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Nullable; + +public final class SubLevelPoiUtil { + + private SubLevelPoiUtil() {} + + public static @Nullable SubLevel getSubLevelForPos(final Level level, final BlockPos pos) { + return Sable.HELPER.getContaining(level, pos); + } + + /** Converts a global entity position to the plotyard BlockPos within the given sublevel. */ + public static BlockPos toPlotyard(final SubLevel sl, final Vec3 globalPos) { + return BlockPos.containing(sl.logicalPose().transformPositionInverse(globalPos)); + } +} diff --git a/common/src/main/resources/sable.mixins.json b/common/src/main/resources/sable.mixins.json index faf5ee1b..e9983cbb 100644 --- a/common/src/main/resources/sable.mixins.json +++ b/common/src/main/resources/sable.mixins.json @@ -141,6 +141,11 @@ "entity.entity_pathfinding.PathNavigationMixin", "entity.entity_pathfinding.RandomPosMixin", "entity.entity_pathfinding.WalkNodeEvaluatorMixin", + "entity.villager_poi.AcquirePoiMixin", + "entity.villager_poi.AssignProfessionFromJobSiteMixin", + "entity.villager_poi.ValidateNearbyPoiMixin", + "entity.villager_poi.SetWalkTargetFromBlockMemoryMixin", + "entity.villager_poi.WorkAtPoiMixin", "entity.entity_rotations_and_riding.BlockMixin", "entity.entity_rotations_and_riding.EntityMixin", "entity.entity_rotations_and_riding.EntityTypeMixin",