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..4c6d42f8c 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,16 +3,19 @@ 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; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; 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 +37,70 @@ 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 = "getClickedFace", + at = @At("HEAD"), + cancellable = true + ) + private void sable$getClickedFace( + final CallbackInfoReturnable cir + ) { + if (this.player == null) + return; + + final SubLevel targetSubLevel = + Sable.HELPER.getContaining( + this.level, + this.getClickedPos() + ); + + if (targetSubLevel == null) + return; + + /* + * Create has already expressed the DeployerFakePlayer's look direction + * in the target SubLevel's local coordinate system. + */ + if (!this.sable$isAlreadyTargetLocal(targetSubLevel)) + return; + + final Vec3 localLook = this.player.getLookAngle(); + + /* + * The deployer looks along the ray toward the target. The clicked surface + * normal therefore points in the opposite direction. + */ + final Direction localClickedFace = + Direction.getNearest( + localLook.x, + localLook.y, + localLook.z + ).getOpposite(); + + cir.setReturnValue(localClickedFace); + } + @Inject(method = "getHorizontalDirection", at = @At("HEAD"), cancellable = true) private void sable$getHorizontalDirection(final CallbackInfoReturnable cir) { if (this.player == null) return; final SubLevel subLevel = Sable.HELPER.getContaining(this.level, this.getClickedPos()); - if (subLevel != null) { - SubLevelHelper.pushEntityLocal(subLevel, this.player); - final Direction dir = this.player.getDirection(); - SubLevelHelper.popEntityLocal(subLevel, this.player); - cir.setReturnValue(dir); - } + if (subLevel == null||this.sable$isAlreadyTargetLocal(subLevel)) return; + + SubLevelHelper.pushEntityLocal(subLevel, this.player); + final Direction dir = this.player.getDirection(); + SubLevelHelper.popEntityLocal(subLevel, this.player); + cir.setReturnValue(dir); } @Inject(method = "getRotation", at = @At("HEAD"), cancellable = true) diff --git a/neoforge/src/main/java/dev/ryanhcode/sable/neoforge/mixin/compatibility/create/deployer/DeployerFakePlayerMixin.java b/neoforge/src/main/java/dev/ryanhcode/sable/neoforge/mixin/compatibility/create/deployer/DeployerFakePlayerMixin.java new file mode 100644 index 000000000..76bd3dafc --- /dev/null +++ b/neoforge/src/main/java/dev/ryanhcode/sable/neoforge/mixin/compatibility/create/deployer/DeployerFakePlayerMixin.java @@ -0,0 +1,38 @@ +package dev.ryanhcode.sable.neoforge.mixin.compatibility.create.deployer; + +import com.simibubi.create.content.kinetics.deployer.DeployerFakePlayer; +import dev.ryanhcode.sable.Sable; +import dev.ryanhcode.sable.api.entity.TargetLocalInteractionEntity; +import dev.ryanhcode.sable.sublevel.SubLevel; +import net.minecraft.world.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(value = DeployerFakePlayer.class, remap = false) +public abstract class DeployerFakePlayerMixin + implements TargetLocalInteractionEntity { + + @Override + public boolean sable$isAlreadyLocalTo( + final SubLevel targetSubLevel + ) { + final Entity self = (Entity) (Object) this; + + /* + * DeployerHandler moves its FakePlayer to the ray origin before + * creating UseOnContext. Inside a Sable SubLevel, this position and + * the rotation supplied by Create are both SubLevel-local. + */ + final SubLevel sourceSubLevel = + Sable.HELPER.getContaining( + self.level(), + self.position() + ); + + /* + * Both instances originate from Sable's loaded SubLevel registry, + * so identity comparison is intentional. + */ + return sourceSubLevel != null + && sourceSubLevel == targetSubLevel; + } +} \ No newline at end of file diff --git a/neoforge/src/main/resources/sable-neoforge.mixins.json b/neoforge/src/main/resources/sable-neoforge.mixins.json index 2c905b21d..b939f236e 100644 --- a/neoforge/src/main/resources/sable-neoforge.mixins.json +++ b/neoforge/src/main/resources/sable-neoforge.mixins.json @@ -93,6 +93,7 @@ "compatibility.create.crushing_wheel.CrushingWheelBlockMixin", "compatibility.create.crushing_wheel_entity_processing.CrushingWheelControllerBlockEntityMixin", "compatibility.create.deployer.DeployerBlockEntityMixin", + "compatibility.create.deployer.DeployerFakePlayerMixin", "compatibility.create.display_link.ClickToLinkBlockItemMixin", "compatibility.create.display_link.DisplayLinkBlockEntityMixin", "compatibility.create.display_link.DisplayLinkBlockMixin",