From d881de44c52f9f1b73878238344663f7377d66cc Mon Sep 17 00:00:00 2001 From: Jakfut Date: Sat, 31 Oct 2026 19:39:30 +0000 Subject: [PATCH 1/8] Validate beacon pyramid layers down to the physical world floor --- .../cubicchunks/mixin/Mixins.java | 4 ++++ .../early/common/MixinTileEntityBeacon.java | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinTileEntityBeacon.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java index d6791100..9c035159 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -73,6 +73,10 @@ public enum Mixins implements IMixins { new MixinBuilder("Allowing lilypads to stay above 256 adn below 0.").addCommonMixins("common.MixinBlockLilyPad") .setPhase(Phase.EARLY) .setApplyIf(() -> true)), + MIXIN_BEACON_HEIGHT( + new MixinBuilder("Validate beacon pyramids below zero").addCommonMixins("common.MixinTileEntityBeacon") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), MIXIN_S01PACKET_JOIN_GAME(new MixinBuilder("Giving the packet info to initialize cubicWorlds for clients.") .addCommonMixins("common.vanillaclient.MixinS01PacketJoinGame") .setPhase(Phase.EARLY) diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinTileEntityBeacon.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinTileEntityBeacon.java new file mode 100644 index 00000000..7ca8395d --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinTileEntityBeacon.java @@ -0,0 +1,22 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityBeacon; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +import com.cardinalstar.cubicchunks.world.ICubicWorld; + +@Mixin(TileEntityBeacon.class) +public abstract class MixinTileEntityBeacon extends TileEntity { + + @ModifyConstant( + method = "func_146003_y", + constant = @Constant(expandZeroConditions = Constant.Condition.LESS_THAN_ZERO), + require = 1) + private int cc$pyramidFloor(int vanillaMinimum) { + return ((ICubicWorld) this.worldObj).getMinHeight(); + } +} From 1dc5ad9fc85b91ec19546a48a0a77db389d3f9d2 Mon Sep 17 00:00:00 2001 From: Jakfut Date: Sat, 31 Oct 2026 19:39:30 +0000 Subject: [PATCH 2/8] Allow vine growth throughout physical world heights --- .../cubicchunks/mixin/Mixins.java | 4 +++ .../early/common/MixinBlockVine_Height.java | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockVine_Height.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java index 9c035159..ed33b930 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -77,6 +77,10 @@ public enum Mixins implements IMixins { new MixinBuilder("Validate beacon pyramids below zero").addCommonMixins("common.MixinTileEntityBeacon") .setPhase(Phase.EARLY) .setApplyIf(() -> true)), + MIXIN_VINE_HEIGHT(new MixinBuilder("Allow vines to grow across the physical height range") + .addCommonMixins("common.MixinBlockVine_Height") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), MIXIN_S01PACKET_JOIN_GAME(new MixinBuilder("Giving the packet info to initialize cubicWorlds for clients.") .addCommonMixins("common.vanillaclient.MixinS01PacketJoinGame") .setPhase(Phase.EARLY) diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockVine_Height.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockVine_Height.java new file mode 100644 index 00000000..41df3d02 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockVine_Height.java @@ -0,0 +1,36 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import java.util.Random; + +import net.minecraft.block.BlockVine; +import net.minecraft.world.World; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +import com.cardinalstar.cubicchunks.world.ICubicWorld; +import com.llamalad7.mixinextras.expression.Definition; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; + +@Mixin(BlockVine.class) +public class MixinBlockVine_Height { + + @ModifyConstant(method = "updateTick", constant = @Constant(intValue = 255), require = 1) + private int cc$growthCeiling(int vanillaMaximum, World world, int x, int y, int z, Random random) { + return ((ICubicWorld) world).getMaxHeight() - 1; + } + + @Definition(id = "y", local = @Local(argsOnly = true, ordinal = 1, type = int.class)) + @Expression("y > 1") + @WrapOperation(method = "updateTick", at = @At("MIXINEXTRAS:EXPRESSION"), require = 1) + private boolean cc$canGrowDown(int y, int vanillaMinimum, Operation original, + @Local(argsOnly = true) World world) { + // Like vanilla's y > 1, vines do not grow into the lowest layer of the world. + return y > ((ICubicWorld) world).getMinHeight() + 1; + } +} From b5a8d36f518d1b6b44e3e5cdc8808a94a2e7447f Mon Sep 17 00:00:00 2001 From: Jakfut Date: Sat, 31 Oct 2026 19:39:30 +0000 Subject: [PATCH 3/8] Find portal frame bottoms below zero within the existing size limit --- .../cubicchunks/mixin/Mixins.java | 4 ++++ .../early/common/MixinBlockPortalSize.java | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockPortalSize.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java index ed33b930..ff26a4dc 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -81,6 +81,10 @@ public enum Mixins implements IMixins { .addCommonMixins("common.MixinBlockVine_Height") .setPhase(Phase.EARLY) .setApplyIf(() -> true)), + MIXIN_PORTAL_FRAME_HEIGHT( + new MixinBuilder("Validate portal frames below zero").addCommonMixins("common.MixinBlockPortalSize") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), MIXIN_S01PACKET_JOIN_GAME(new MixinBuilder("Giving the packet info to initialize cubicWorlds for clients.") .addCommonMixins("common.vanillaclient.MixinS01PacketJoinGame") .setPhase(Phase.EARLY) diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockPortalSize.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockPortalSize.java new file mode 100644 index 00000000..8fc50d29 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockPortalSize.java @@ -0,0 +1,22 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import net.minecraft.block.BlockPortal; +import net.minecraft.world.World; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; + +import com.cardinalstar.cubicchunks.world.ICubicWorld; + +@Mixin(BlockPortal.Size.class) +public class MixinBlockPortalSize { + + @ModifyConstant( + method = "", + constant = @Constant(expandZeroConditions = Constant.Condition.GREATER_THAN_ZERO), + require = 1) + private int cc$frameFloor(int vanillaMinimum, World world, int x, int y, int z, int axis) { + return ((ICubicWorld) world).getMinHeight(); + } +} From 55fb31c72e5be998456fb8a74c59353d83dab3ff Mon Sep 17 00:00:00 2001 From: Jakfut Date: Sat, 31 Oct 2026 19:39:30 +0000 Subject: [PATCH 4/8] Use physical limits for vanilla piston extension --- .../cubicchunks/mixin/Mixins.java | 4 ++ .../common/MixinBlockPistonBase_Height.java | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockPistonBase_Height.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java index ff26a4dc..cb90f0ce 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -85,6 +85,10 @@ public enum Mixins implements IMixins { new MixinBuilder("Validate portal frames below zero").addCommonMixins("common.MixinBlockPortalSize") .setPhase(Phase.EARLY) .setApplyIf(() -> true)), + MIXIN_PISTON_HEIGHT(new MixinBuilder("Keep vanilla piston movement inside physical bounds") + .addCommonMixins("common.MixinBlockPistonBase_Height") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), MIXIN_S01PACKET_JOIN_GAME(new MixinBuilder("Giving the packet info to initialize cubicWorlds for clients.") .addCommonMixins("common.vanillaclient.MixinS01PacketJoinGame") .setPhase(Phase.EARLY) diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockPistonBase_Height.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockPistonBase_Height.java new file mode 100644 index 00000000..d14a0a47 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinBlockPistonBase_Height.java @@ -0,0 +1,49 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import net.minecraft.block.BlockPistonBase; +import net.minecraft.world.World; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import com.cardinalstar.cubicchunks.world.ICubicWorld; +import com.llamalad7.mixinextras.expression.Expression; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; + +@Mixin(BlockPistonBase.class) +public class MixinBlockPistonBase_Height { + + // Like vanilla's y <= 0, nothing moves into the lowest layer of the world. + @Expression("? <= 0") + @WrapOperation(method = "canExtend", at = @At("MIXINEXTRAS:EXPRESSION"), require = 1) + private static boolean cc$canExtendBelowFloor(int y, int zero, Operation original, + @Local(argsOnly = true) World world) { + return y <= ((ICubicWorld) world).getMinHeight(); + } + + @Expression("? <= 0") + @WrapOperation(method = "tryExtend", at = @At("MIXINEXTRAS:EXPRESSION"), require = 1) + private boolean cc$tryExtendBelowFloor(int y, int zero, Operation original, + @Local(argsOnly = true) World world) { + return y <= ((ICubicWorld) world).getMinHeight(); + } + + @Redirect( + method = "canExtend", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;getHeight()I"), + require = 1) + private static int cc$canExtendCeiling(World world) { + return ((ICubicWorld) world).getMaxHeight(); + } + + @Redirect( + method = "tryExtend", + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;getHeight()I"), + require = 1) + private int cc$tryExtendCeiling(World world) { + return ((ICubicWorld) world).getMaxHeight(); + } +} From b05f84d68ff50a68d691456a4080d00b4994b1c9 Mon Sep 17 00:00:00 2001 From: Jakfut Date: Sat, 31 Oct 2026 19:39:30 +0000 Subject: [PATCH 5/8] Serialize bed height as a signed integer on both network endpoints --- .../cubicchunks/mixin/Mixins.java | 4 ++ .../early/common/MixinS0APacketUseBed.java | 46 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinS0APacketUseBed.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java index cb90f0ce..d93a8361 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -105,6 +105,10 @@ public enum Mixins implements IMixins { .addCommonMixins("common.MixinS28PacketEffect") .setPhase(Phase.EARLY) .setApplyIf(() -> true)), + MIXIN_S0A_HEIGHT_LIMITS(new MixinBuilder("Changing packet S0A to read and write full integer Y values.") + .addCommonMixins("common.MixinS0APacketUseBed") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), MIXIN_S07PACKET_RESPAWN(new MixinBuilder("Giving respawn packets info to initialize cubicWorlds for clients.") .addCommonMixins("common.vanillaclient.MixinS07PacketRespawn") .setPhase(Phase.EARLY) diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinS0APacketUseBed.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinS0APacketUseBed.java new file mode 100644 index 00000000..e977ef1c --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinS0APacketUseBed.java @@ -0,0 +1,46 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import net.minecraft.network.PacketBuffer; +import net.minecraft.network.play.server.S0APacketUseBed; + +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.CallbackInfo; + +import io.netty.buffer.ByteBuf; + +@Mixin(S0APacketUseBed.class) +public class MixinS0APacketUseBed { + + @Shadow + private int field_149096_c; + + @Redirect( + method = "readPacketData", + at = @At(value = "INVOKE", target = "Lnet/minecraft/network/PacketBuffer;readByte()B"), + require = 1) + private byte skipVanillaYRead(PacketBuffer data) { + return 0; + } + + @Inject( + method = "readPacketData", + at = @At(value = "INVOKE", target = "Lnet/minecraft/network/PacketBuffer;readInt()I", ordinal = 2), + require = 1) + private void readFullY(PacketBuffer data, CallbackInfo ci) { + this.field_149096_c = data.readInt(); + } + + @Redirect( + method = "writePacketData", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/network/PacketBuffer;writeByte(I)Lio/netty/buffer/ByteBuf;"), + require = 1) + private ByteBuf writeFullY(PacketBuffer data, int ignored) { + return data.writeInt(this.field_149096_c); + } +} From 8b041cf03df8161c0a4e60adf16bb0e55a107c5a Mon Sep 17 00:00:00 2001 From: Jakfut Date: Sat, 31 Oct 2026 19:39:30 +0000 Subject: [PATCH 6/8] Parse explicit spawn coordinates using the target world height bounds --- .../cubicchunks/mixin/Mixins.java | 4 +++ .../MixinCommandSetDefaultSpawnpoint.java | 28 +++++++++++++++++ .../common/MixinCommandSetSpawnpoint.java | 30 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCommandSetDefaultSpawnpoint.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCommandSetSpawnpoint.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java index d93a8361..55782c95 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -266,6 +266,10 @@ public enum Mixins implements IMixins { .addCommonMixins("common.MixinNetHandlerPlayServer") .setPhase(Phase.EARLY) .setApplyIf(() -> true)), + MIXIN_SPAWN_COMMAND_HEIGHT(new MixinBuilder("Accept explicit spawn coordinates within physical world bounds") + .addCommonMixins("common.MixinCommandSetSpawnpoint", "common.MixinCommandSetDefaultSpawnpoint") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), // ============================================================= // Client Mixins diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCommandSetDefaultSpawnpoint.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCommandSetDefaultSpawnpoint.java new file mode 100644 index 00000000..a4616208 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCommandSetDefaultSpawnpoint.java @@ -0,0 +1,28 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import net.minecraft.command.ICommandSender; +import net.minecraft.command.server.CommandSetDefaultSpawnpoint; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import com.cardinalstar.cubicchunks.world.ICubicWorld; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; + +@Mixin(CommandSetDefaultSpawnpoint.class) +public class MixinCommandSetDefaultSpawnpoint { + + @WrapOperation( + method = "processCommand", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/command/server/CommandSetDefaultSpawnpoint;parseIntBounded(Lnet/minecraft/command/ICommandSender;Ljava/lang/String;II)I", + ordinal = 1), + require = 1) + private int cc$parseHeight(ICommandSender sender, String value, int minimum, int maximum, + Operation original) { + ICubicWorld world = (ICubicWorld) sender.getEntityWorld(); + return original.call(sender, value, world.getMinHeight(), world.getMaxHeight() - 1); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCommandSetSpawnpoint.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCommandSetSpawnpoint.java new file mode 100644 index 00000000..28c47b71 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinCommandSetSpawnpoint.java @@ -0,0 +1,30 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import net.minecraft.command.CommandSetSpawnpoint; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.player.EntityPlayerMP; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import com.cardinalstar.cubicchunks.world.ICubicWorld; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; + +@Mixin(CommandSetSpawnpoint.class) +public class MixinCommandSetSpawnpoint { + + @WrapOperation( + method = "processCommand", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/command/CommandSetSpawnpoint;parseIntBounded(Lnet/minecraft/command/ICommandSender;Ljava/lang/String;II)I", + ordinal = 1), + require = 1) + private int cc$parseHeight(ICommandSender sender, String value, int minimum, int maximum, + Operation original, @Local EntityPlayerMP player) { + ICubicWorld world = (ICubicWorld) player.worldObj; + return original.call(sender, value, world.getMinHeight(), world.getMaxHeight() - 1); + } +} From 5eaf726d3acb1ad1fb35325d2810ec33efabd093 Mon Sep 17 00:00:00 2001 From: Jakfut Date: Sat, 31 Oct 2026 19:39:30 +0000 Subject: [PATCH 7/8] Choose safe signed-height spread surfaces with a bounded search --- .../cubicchunks/mixin/Mixins.java | 4 + .../common/MixinSpreadPlayerPosition.java | 53 +++++++++++ .../cubicchunks/util/SpreadPlayerSurface.java | 87 +++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinSpreadPlayerPosition.java create mode 100644 src/main/java/com/cardinalstar/cubicchunks/util/SpreadPlayerSurface.java diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java index 55782c95..8c1af44f 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/Mixins.java @@ -270,6 +270,10 @@ public enum Mixins implements IMixins { .addCommonMixins("common.MixinCommandSetSpawnpoint", "common.MixinCommandSetDefaultSpawnpoint") .setPhase(Phase.EARLY) .setApplyIf(() -> true)), + MIXIN_SPREAD_PLAYER_HEIGHT(new MixinBuilder("Find safe spread-player surfaces at signed heights with bounded work") + .addCommonMixins("common.MixinSpreadPlayerPosition") + .setPhase(Phase.EARLY) + .setApplyIf(() -> true)), // ============================================================= // Client Mixins diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinSpreadPlayerPosition.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinSpreadPlayerPosition.java new file mode 100644 index 00000000..197094f1 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/common/MixinSpreadPlayerPosition.java @@ -0,0 +1,53 @@ +package com.cardinalstar.cubicchunks.mixin.early.common; + +import net.minecraft.util.MathHelper; +import net.minecraft.world.World; + +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; + +import com.cardinalstar.cubicchunks.util.SpreadPlayerSurface; +import com.cardinalstar.cubicchunks.world.cube.ICubeProvider; + +@Mixin(targets = "net.minecraft.command.CommandSpreadPlayers$Position") +public class MixinSpreadPlayerPosition { + + @Shadow + double field_111101_a; + + @Shadow + double field_111100_b; + + @Inject(method = "func_111098_b", at = @At("HEAD"), cancellable = true, require = 1) + private void cc$checkSurface(World world, CallbackInfoReturnable cir) { + if (world.getChunkProvider() instanceof ICubeProvider) { + int landing = this.cc$landing(world); + // Where the loaded terrain does not decide, vanilla's check runs, as without this fix. + if (landing != SpreadPlayerSurface.NOT_LOADED) { + cir.setReturnValue(landing != SpreadPlayerSurface.UNSAFE); + } + } + } + + @Inject(method = "func_111092_a", at = @At("HEAD"), cancellable = true, require = 1) + private void cc$landingHeight(World world, CallbackInfoReturnable cir) { + if (world.getChunkProvider() instanceof ICubeProvider) { + int landing = this.cc$landing(world); + // A check that vanilla decided, or a teammate's landing, may have loaded unsafe terrain since: then land + // like vanilla, as the check did not reject it. + if (landing != SpreadPlayerSurface.NOT_LOADED && landing != SpreadPlayerSurface.UNSAFE) { + cir.setReturnValue(landing); + } + } + } + + @Unique + private int cc$landing(World world) { + return SpreadPlayerSurface + .find(world, MathHelper.floor_double(this.field_111101_a), MathHelper.floor_double(this.field_111100_b)); + } +} diff --git a/src/main/java/com/cardinalstar/cubicchunks/util/SpreadPlayerSurface.java b/src/main/java/com/cardinalstar/cubicchunks/util/SpreadPlayerSurface.java new file mode 100644 index 00000000..d0370ae0 --- /dev/null +++ b/src/main/java/com/cardinalstar/cubicchunks/util/SpreadPlayerSurface.java @@ -0,0 +1,87 @@ +package com.cardinalstar.cubicchunks.util; + +import javax.annotation.Nullable; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.init.Blocks; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; + +import com.cardinalstar.cubicchunks.api.IColumn; +import com.cardinalstar.cubicchunks.world.ICubicWorld; +import com.cardinalstar.cubicchunks.world.cube.Cube; +import com.cardinalstar.cubicchunks.world.cube.ICubeProvider; + +/** + * A bounded search around known column surfaces, never a full physical-height scan. It only reads loaded cubes and + * never loads or generates terrain; where the loaded terrain does not decide, callers fall back to vanilla's search. + */ +public final class SpreadPlayerSurface { + + /** The floor is liquid or fire, the space above it is blocked, or it is at the top of the world. */ + public static final int UNSAFE = Integer.MIN_VALUE; + /** The column, its floor or the space above the floor is not loaded, or the search budget ran out. */ + public static final int NOT_LOADED = Integer.MIN_VALUE + 1; + + private SpreadPlayerSurface() {} + + /** The landing height at x, z, or {@link #UNSAFE} or {@link #NOT_LOADED}. */ + public static int find(World world, int x, int z) { + ICubicWorld cubic = (ICubicWorld) world; + int minimum = cubic.getMinHeight(); + int maximum = cubic.getMaxHeight(); + Chunk chunk = ((ICubeProvider) world.getChunkProvider()).getLoadedColumn(x >> 4, z >> 4); + if (chunk == null) return NOT_LOADED; + IColumn column = (IColumn) chunk; + // The opacity index gives the known top; resident cubes also expose transparent platforms above it. + int top = column.getOpacityIndex() + .getTopBlockY(x & 15, z & 15); + int budget = 256; + scan: for (Cube cube : column.getLoadedCubes((maximum - 1) >> 4, minimum >> 4)) { + int cubeTop = Math.min( + cube.getCoords() + .getMaxBlockY(), + maximum - 1); + if (cubeTop <= top) break; + if (cube.isEmpty()) continue; + int bottom = Math.max( + cube.getCoords() + .getMinBlockY(), + minimum); + for (int y = cubeTop; y >= bottom && y > top; y--) { + if (--budget < 0) return NOT_LOADED; + if (cube.getBlock(x, y, z) + .getMaterial() != Material.air) { + top = y; + break scan; + } + } + } + if (top < minimum) return NOT_LOADED; + if (top >= maximum - 1) return UNSAFE; + for (int y = top; y >= minimum && budget-- > 0; y--) { + Block floor = loadedBlock(column, maximum, x, y, z); + if (floor == null) return NOT_LOADED; + Material material = floor.getMaterial(); + if (material.isLiquid() || material == Material.fire) return UNSAFE; + if (!material.blocksMovement()) continue; + Block feet = loadedBlock(column, maximum, x, y + 1, z); + Block head = loadedBlock(column, maximum, x, y + 2, z); + if (feet == null || head == null) return NOT_LOADED; + return clear(feet.getMaterial()) && clear(head.getMaterial()) ? y + 1 : UNSAFE; + } + return NOT_LOADED; + } + + @Nullable + private static Block loadedBlock(IColumn column, int maximum, int x, int y, int z) { + if (y >= maximum) return Blocks.air; + Cube cube = column.getLoadedCube(y >> 4); + return cube == null ? null : cube.getBlock(x, y, z); + } + + private static boolean clear(Material material) { + return !material.blocksMovement() && !material.isLiquid() && material != Material.fire; + } +} From cf6f01edb1b902ad83c37786e17c083eddf923c5 Mon Sep 17 00:00:00 2001 From: Jakfut Date: Sat, 31 Oct 2026 19:39:30 +0000 Subject: [PATCH 8/8] Clear client spawn collisions below zero with a bounded upward search --- .../mixin/early/client/MixinEntity.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntity.java b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntity.java index 7ab17508..341f08fa 100644 --- a/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntity.java +++ b/src/main/java/com/cardinalstar/cubicchunks/mixin/early/client/MixinEntity.java @@ -7,14 +7,44 @@ 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.CallbackInfo; +import com.cardinalstar.cubicchunks.world.ICubicWorld; import com.llamalad7.mixinextras.expression.Definition; import com.llamalad7.mixinextras.expression.Expression; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; @Mixin(Entity.class) -public class MixinEntity { +public abstract class MixinEntity { + + @Shadow + public World worldObj; + + @Shadow + public double posX; + + @Shadow + public double posZ; + + @Shadow + public abstract void setPosition(double x, double y, double z); + + @Definition(id = "posY", field = "Lnet/minecraft/entity/Entity;posY:D") + @Expression("this.posY > 0.0") + @WrapOperation(method = "preparePlayerToSpawn", at = @At("MIXINEXTRAS:EXPRESSION"), require = 1) + private boolean cc$canClearSpawnCollision(double y, double zero, Operation original) { + // Include the first air layer above the build ceiling, then stop even if obstructed. + return y <= ((ICubicWorld) this.worldObj).getMaxHeight(); + } + + @Inject(method = "preparePlayerToSpawn", at = @At("RETURN"), require = 1) + private void cc$finishSpawnPosition(CallbackInfo ci) { + if (this.worldObj != null) { + this.setPosition(this.posX, this.posY, this.posZ); + } + } @Shadow public double posY;