From b4d78ab27be56655501018e7fb6dbbde67406a52 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 21 May 2026 09:52:06 +0200 Subject: [PATCH 1/6] Kick armor stand off sub-level when not grounded --- .../ArmorStandMixin.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java new file mode 100644 index 00000000..2102945f --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java @@ -0,0 +1,33 @@ +package dev.ryanhcode.sable.mixin.entity.entity_sublevel_collision; + +import dev.ryanhcode.sable.Sable; +import dev.ryanhcode.sable.api.entity.EntitySubLevelUtil; +import dev.ryanhcode.sable.sublevel.SubLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.level.Level; +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.CallbackInfo; + +@Mixin(ArmorStand.class) +public abstract class ArmorStandMixin extends Entity { + + public ArmorStandMixin(final EntityType entityType, final Level level) { + super(entityType, level); + } + + @Inject(method = "tick", at = @At("TAIL")) + private void sable$postTick(final CallbackInfo ci) { + if (this.level().isClientSide) return; + + final SubLevel containingSubLevel = Sable.HELPER.getContaining(this); + if (containingSubLevel == null) return; + + if (this.onGround()) return; + + EntitySubLevelUtil.kickEntity(containingSubLevel, this); + } +} From 76acbd68cb5c26694fc8b487d4dd69941baa00f8 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 21 May 2026 09:53:45 +0200 Subject: [PATCH 2/6] Remove 'minecraft:armor_stand' from destroy list --- .../data/sable/tags/entity_type/destroy_when_leaving_plot.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/src/main/resources/data/sable/tags/entity_type/destroy_when_leaving_plot.json b/common/src/main/resources/data/sable/tags/entity_type/destroy_when_leaving_plot.json index 131ae636..cefca84d 100644 --- a/common/src/main/resources/data/sable/tags/entity_type/destroy_when_leaving_plot.json +++ b/common/src/main/resources/data/sable/tags/entity_type/destroy_when_leaving_plot.json @@ -2,11 +2,10 @@ "replace": false, "values": [ { "id": "exposure:camera_stand", "required": false }, - "minecraft:armor_stand", "minecraft:minecart", "minecraft:hopper_minecart", "minecraft:chest_minecart", "minecraft:furnace_minecart", "minecraft:tnt_minecart" ] -} \ No newline at end of file +} From efbd30a13d626d13e5f487995ba302dc9c037227 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 21 May 2026 10:12:55 +0200 Subject: [PATCH 3/6] Add ArmorStandMixin to sable.mixins.json --- common/src/main/resources/sable.mixins.json | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/resources/sable.mixins.json b/common/src/main/resources/sable.mixins.json index faf5ee1b..e2f71feb 100644 --- a/common/src/main/resources/sable.mixins.json +++ b/common/src/main/resources/sable.mixins.json @@ -149,6 +149,7 @@ "entity.entity_rotations_and_riding.ServerEntityMixin", "entity.entity_rotations_and_riding.ServerPlayerMixin", "entity.entity_sublevel_collision.AbstractMinecartMixin", + "entity.entity_sublevel_collision.ArmorStandMixin", "entity.entity_sublevel_collision.EntityMixin", "entity.entity_sublevel_collision.ItemEntityMixin", "entity.entity_sublevel_collision.LevelMixin", From 3e8c9756f8ff96ed89bb27c9e3c0876d73058c2c Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 21 May 2026 14:20:30 +0200 Subject: [PATCH 4/6] Hopefully fix armor stands going invisible --- .../ClientPacketListenerMixin.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entities_stick_sublevels/ClientPacketListenerMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entities_stick_sublevels/ClientPacketListenerMixin.java index b8bc2e5e..6a34aac0 100644 --- a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entities_stick_sublevels/ClientPacketListenerMixin.java +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entities_stick_sublevels/ClientPacketListenerMixin.java @@ -103,13 +103,15 @@ public abstract class ClientPacketListenerMixin { final SubLevel existingSubLevel = Sable.HELPER.getContaining(this.level, entity.position()); if (subLevel != null && actuallyInSubLevel && existingSubLevel != subLevel) { - entity.setPos(subLevel.logicalPose().transformPositionInverse(entity.position())); + final Vec3 newPos = subLevel.logicalPose().transformPositionInverse(entity.position()); + entity.moveTo(newPos.x, newPos.y, newPos.z); } else if (existingSubLevel != null && subLevel == null) { - entity.setPos(existingSubLevel.logicalPose().transformPosition(entity.position())); + final Vec3 newPos = existingSubLevel.logicalPose().transformPosition(entity.position()); + entity.moveTo(newPos.x, newPos.y, newPos.z); } entity.lerpTo(pX, pY, pZ, pYRot, pXRot, pLerpSteps); extension.sable$setPlotPosition(null); } } -} \ No newline at end of file +} From 278232f12871e874db082a2719c4197d0986e223 Mon Sep 17 00:00:00 2001 From: JpegN Date: Thu, 21 May 2026 14:21:33 +0200 Subject: [PATCH 5/6] Let armor stands land and attach on sublevels --- .../ArmorStandMixin.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java index 2102945f..0d8fe960 100644 --- a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java @@ -7,6 +7,7 @@ import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec3; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -24,10 +25,19 @@ public ArmorStandMixin(final EntityType entityType, final Level level) { if (this.level().isClientSide) return; final SubLevel containingSubLevel = Sable.HELPER.getContaining(this); - if (containingSubLevel == null) return; - if (this.onGround()) return; - - EntitySubLevelUtil.kickEntity(containingSubLevel, this); + if (containingSubLevel != null) { + if (!this.onGround()) { + EntitySubLevelUtil.kickEntity(containingSubLevel, this); + } + } else if (this.onGround()) { + final SubLevel landed = Sable.HELPER.getTrackingSubLevel(this); + if (landed != null) { + final Vec3 shipyardPos = landed.logicalPose().transformPositionInverse(this.position()); + final Vec3 shipyardVel = landed.logicalPose().transformNormalInverse(this.getDeltaMovement()); + this.moveTo(shipyardPos.x, shipyardPos.y, shipyardPos.z); + this.setDeltaMovement(shipyardVel); + } + } } } From e68a080b5afffaedad9cea56b7600aa362e791b6 Mon Sep 17 00:00:00 2001 From: JpegN Date: Thu, 21 May 2026 15:57:11 +0200 Subject: [PATCH 6/6] Fix armor stand landing and culling --- .../entity_rendering/LevelRendererMixin.java | 2 +- .../ArmorStandMixin.java | 21 ++++++++++++++---- .../mixin/entity/no_culling/EntityMixin.java | 22 +++++++++++++++++++ common/src/main/resources/sable.mixins.json | 1 + 4 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 common/src/main/java/dev/ryanhcode/sable/mixin/entity/no_culling/EntityMixin.java diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_rendering/LevelRendererMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_rendering/LevelRendererMixin.java index b5450c09..59e7ae5e 100644 --- a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_rendering/LevelRendererMixin.java +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_rendering/LevelRendererMixin.java @@ -44,7 +44,7 @@ private void renderEntityOnSubLevel(final Entity entity, @Local(ordinal = 5) final LocalDoubleRef entityZ, @Share("renderPose") final LocalRef renderPoseShare) { // Render the entity on the data - final ClientSubLevel subLevel = (ClientSubLevel) Sable.HELPER.getContaining(entity); + final ClientSubLevel subLevel = Sable.HELPER.getContainingClient(entity.getX(), entity.getZ()); if (subLevel == null) { // Tracking sub-levels diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java index 0d8fe960..530836e8 100644 --- a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/entity_sublevel_collision/ArmorStandMixin.java @@ -8,7 +8,11 @@ import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.block.state.BlockState; import org.spongepowered.asm.mixin.Mixin; +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.CallbackInfo; @@ -20,6 +24,9 @@ public ArmorStandMixin(final EntityType entityType, final Level level) { super(entityType, level); } + @Unique + private int sable$lastAttachedTick; + @Inject(method = "tick", at = @At("TAIL")) private void sable$postTick(final CallbackInfo ci) { if (this.level().isClientSide) return; @@ -27,16 +34,22 @@ public ArmorStandMixin(final EntityType entityType, final Level level) { final SubLevel containingSubLevel = Sable.HELPER.getContaining(this); if (containingSubLevel != null) { - if (!this.onGround()) { + if (this.tickCount - this.sable$lastAttachedTick > 1 && !this.onGround()) { EntitySubLevelUtil.kickEntity(containingSubLevel, this); } } else if (this.onGround()) { final SubLevel landed = Sable.HELPER.getTrackingSubLevel(this); if (landed != null) { final Vec3 shipyardPos = landed.logicalPose().transformPositionInverse(this.position()); - final Vec3 shipyardVel = landed.logicalPose().transformNormalInverse(this.getDeltaMovement()); - this.moveTo(shipyardPos.x, shipyardPos.y, shipyardPos.z); - this.setDeltaMovement(shipyardVel); + final BlockPos belowPos = BlockPos.containing(shipyardPos.x, shipyardPos.y - 0.5, shipyardPos.z); + final BlockState belowState = this.level().getBlockState(belowPos); + if (belowState.isFaceSturdy(this.level(), belowPos, Direction.UP) + && Math.abs(shipyardPos.y - (belowPos.getY() + 1)) < 0.1) { + final Vec3 shipyardVel = landed.logicalPose().transformNormalInverse(this.getDeltaMovement()); + this.moveTo(shipyardPos.x, shipyardPos.y, shipyardPos.z); + this.setDeltaMovement(shipyardVel); + this.sable$lastAttachedTick = this.tickCount; + } } } } diff --git a/common/src/main/java/dev/ryanhcode/sable/mixin/entity/no_culling/EntityMixin.java b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/no_culling/EntityMixin.java new file mode 100644 index 00000000..a8bba03b --- /dev/null +++ b/common/src/main/java/dev/ryanhcode/sable/mixin/entity/no_culling/EntityMixin.java @@ -0,0 +1,22 @@ +package dev.ryanhcode.sable.mixin.entity.no_culling; + +import dev.ryanhcode.sable.api.entity.EntitySubLevelUtil; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.Level; +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.CallbackInfo; + +@Mixin(Entity.class) +public abstract class EntityMixin { + + @Inject(method = "", at = @At("TAIL")) + private void sable$disableCullingForRetained(final EntityType type, final Level level, final CallbackInfo ci) { + final Entity self = (Entity) (Object) this; + if (!EntitySubLevelUtil.shouldKick(self)) { + self.noCulling = true; + } + } +} \ No newline at end of file diff --git a/common/src/main/resources/sable.mixins.json b/common/src/main/resources/sable.mixins.json index e2f71feb..b3900eeb 100644 --- a/common/src/main/resources/sable.mixins.json +++ b/common/src/main/resources/sable.mixins.json @@ -41,6 +41,7 @@ "entity.entity_sublevel_collision.CameraMixin", "entity.entity_swimming.CameraMixin", "entity.parrot.ParrotMixin", + "entity.no_culling.EntityMixin", "loaded_chunk_debug.BlockUpdatePacketMixin", "loaded_chunk_debug.ChunkBorderRendererMixin", "loaded_chunk_debug.ClientChunkCacheStorageAccessor",