From 360946776502eed4d06cec63f60cd234acdaa0ed Mon Sep 17 00:00:00 2001 From: melon-444 <1626622980@qq.com> Date: Thu, 17 Sep 2026 20:42:21 +0800 Subject: [PATCH 1/2] Fix deployer placement orientation on rotated sub-levels --- .../entity/TargetLocalInteractionEntity.java | 20 ++++++++++ .../BlockPlaceContextMixin.java | 25 ++++++++---- .../block_placement/UseOnContextMixin.java | 24 +++++++++--- .../deployer/DeployerFakePlayerMixin.java | 38 +++++++++++++++++++ .../main/resources/sable-neoforge.mixins.json | 1 + 5 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 common/src/main/java/dev/ryanhcode/sable/api/entity/TargetLocalInteractionEntity.java create mode 100644 neoforge/src/main/java/dev/ryanhcode/sable/neoforge/mixin/compatibility/create/deployer/DeployerFakePlayerMixin.java diff --git a/common/src/main/java/dev/ryanhcode/sable/api/entity/TargetLocalInteractionEntity.java b/common/src/main/java/dev/ryanhcode/sable/api/entity/TargetLocalInteractionEntity.java new file mode 100644 index 000000000..8e6de8566 --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/api/entity/TargetLocalInteractionEntity.java @@ -0,0 +1,20 @@ +package dev.ryanhcode.sable.api.entity; + +import dev.ryanhcode.sable.sublevel.SubLevel; + +/** + * Implemented by synthetic interaction entities whose position and rotation + * may already be expressed in a target SubLevel's local coordinate system. + * + *
When this returns {@code true}, Sable must not inverse-transform the + * entity into the same SubLevel a second time.
+ */ +public interface TargetLocalInteractionEntity { + + /** + * @param targetSubLevel the SubLevel containing the interaction target + * @return true when this entity is already expressed in the target's + * local coordinate system + */ + boolean sable$isAlreadyLocalTo(SubLevel targetSubLevel); +} \ No newline at end of file diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/block_placement/BlockPlaceContextMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/block_placement/BlockPlaceContextMixin.java index 408b3535f..0b8f43af3 100644 --- a/common/src/main/java/dev/ryanhcode/sable/mixin/block_placement/BlockPlaceContextMixin.java +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/block_placement/BlockPlaceContextMixin.java @@ -2,6 +2,7 @@ import dev.ryanhcode.sable.Sable; import dev.ryanhcode.sable.api.SubLevelHelper; +import dev.ryanhcode.sable.api.entity.TargetLocalInteractionEntity; import dev.ryanhcode.sable.companion.math.BoundingBox3d; import dev.ryanhcode.sable.companion.math.BoundingBox3dc; import dev.ryanhcode.sable.api.math.LevelReusedVectors; @@ -40,6 +41,16 @@ public abstract class BlockPlaceContextMixin extends UseOnContext { @Shadow protected boolean replaceClicked; + @Unique + private static boolean sable$isAlreadyTargetLocal( + final Entity entity, + final SubLevel targetSubLevel + ) { + return entity instanceof + final TargetLocalInteractionEntity localEntity + && localEntity.sable$isAlreadyLocalTo(targetSubLevel); + } + public BlockPlaceContextMixin(final Player pPlayer, final InteractionHand pHand, final BlockHitResult pHitResult) { super(pPlayer, pHand, pHitResult); } @@ -51,6 +62,7 @@ public BlockPlaceContextMixin(final Player pPlayer, final InteractionHand pHand, private Direction sable$getFacingAxis(final Entity player, final Direction.Axis axis) { final SubLevel subLevel = Sable.HELPER.getContaining(this.getLevel(), this.getClickedPos()); + if(subLevel==null||sable$isAlreadyTargetLocal(player, subLevel)) return Direction.getFacingAxis(player,axis); if (subLevel != null) { SubLevelHelper.pushEntityLocal(subLevel, player); final Direction facingAxis = Direction.getFacingAxis(player, axis); @@ -65,14 +77,13 @@ public BlockPlaceContextMixin(final Player pPlayer, final InteractionHand pHand, private Direction[] sable$orderedByNearest(final Entity player) { final SubLevel subLevel = Sable.HELPER.getContaining(this.getLevel(), this.getClickedPos()); - if (subLevel != null) { - SubLevelHelper.pushEntityLocal(subLevel, player); - final Direction[] nearest = Direction.orderedByNearest(player); - SubLevelHelper.popEntityLocal(subLevel, player); - return nearest; - } + if (subLevel == null || sable$isAlreadyTargetLocal(player, subLevel)) return Direction.orderedByNearest(player); + + SubLevelHelper.pushEntityLocal(subLevel, player); + final Direction[] nearest = Direction.orderedByNearest(player); + SubLevelHelper.popEntityLocal(subLevel, player); + return nearest; - return Direction.orderedByNearest(player); } @Inject(method = "canPlace", at = @At("HEAD"), cancellable = true) diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/block_placement/UseOnContextMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/block_placement/UseOnContextMixin.java index 589b83e76..87d4f8396 100644 --- a/common/src/main/java/dev/ryanhcode/sable/mixin/block_placement/UseOnContextMixin.java +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/block_placement/UseOnContextMixin.java @@ -3,6 +3,7 @@ import dev.ryanhcode.sable.Sable; import dev.ryanhcode.sable.api.SubLevelHelper; +import dev.ryanhcode.sable.api.entity.TargetLocalInteractionEntity; import dev.ryanhcode.sable.sublevel.SubLevel; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -13,6 +14,7 @@ import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @@ -34,18 +36,28 @@ public abstract class UseOnContextMixin { @Shadow public abstract BlockPos getClickedPos(); + @Unique + private boolean sable$isAlreadyTargetLocal( + final SubLevel targetSubLevel + ) { + return this.player instanceof + final TargetLocalInteractionEntity localEntity + && localEntity.sable$isAlreadyLocalTo(targetSubLevel); + } + + @Inject(method = "getHorizontalDirection", at = @At("HEAD"), cancellable = true) private void sable$getHorizontalDirection(final CallbackInfoReturnable