mojira.dev
MC-129886

Trying to place a block underneath min_y does not give an error message

The bug

When attempting to place a block above (min_y + height) (default is 320 in minecraft:overworld and 256 in minecraft:the_nether and minecraft:the_end), an error message stating that this is not possible is displayed. When attempting to place a block outwith the opposite extreme, there is no error message. Compare attached screenshots.

Code analysis

Code analysis by @unknown can be found in this comment.

Linked issues

Attachments

Comments 12

CreeperMagnet_

Invalid, as you usually can't get there. But in creative you can, so it's up to mojang to decide.

As of 18w21a the hand does not animate.

Not invalid - you can get below 0 in any void-based world by water ladder. I believe water is allowed in The End, which is a void world.

And of course, the ever-popular Skyblock style worlds basically require this water ladder technique, which could easily lead to players attempting to build below 0.

CreeperMagnet_

Can confirm for 1.13-pre4.

In 1.16.1 and 20w27a

2 more comments

Added "vanilla-parity" tags since Bedrock Edition's latest beta has added these error/warning messages.

[media]

Can confirm in 1.17.1.

Can confirm in 21w44a.

can confirm in 1.20 Pre-release 1.

Code Analysis:

The following is based on a decompiled version of Minecraft 1.20.1 using MCP-Reborn.

net.minecraft.server.network.ServerGamePacketListenerImpl.java // Original

public class ServerGamePacketListenerImpl implements ServerPlayerConnection, TickablePacketListener, ServerGamePacketListener {
    ...
    public void handleUseItemOn(ServerboundUseItemOnPacket p_9930_) {
        PacketUtils.ensureRunningOnSameThread(p_9930_, this, this.player.serverLevel());
        this.player.connection.ackBlockChangesUpTo(p_9930_.getSequence());
        ServerLevel serverlevel = this.player.serverLevel();
        InteractionHand interactionhand = p_9930_.getHand();
        ItemStack itemstack = this.player.getItemInHand(interactionhand);
        if (itemstack.isItemEnabled(serverlevel.enabledFeatures())) {
            BlockHitResult blockhitresult = p_9930_.getHitResult();
            Vec3 vec3 = blockhitresult.getLocation();
            BlockPos blockpos = blockhitresult.getBlockPos();
            Vec3 vec31 = Vec3.atCenterOf(blockpos);
            if (!(this.player.getEyePosition().distanceToSqr(vec31) > MAX_INTERACTION_DISTANCE)) {
                Vec3 vec32 = vec3.subtract(vec31);
                double d0 = 1.0000001 D;
                if (Math.abs(vec32.x()) < 1.0000001 D && Math.abs(vec32.y()) < 1.0000001 D && Math.abs(vec32.z()) < 1.0000001 D) {
                    Direction direction = blockhitresult.getDirection();
                    this.player.resetLastActionTime();
                    int i = this.player.level().getMaxBuildHeight();
                    if (blockpos.getY() < i) {
                        if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockpos.getX() + 0.5 D, (double) blockpos.getY() + 0.5 D, (double) blockpos.getZ() + 0.5 D) < 64.0 D && serverlevel.mayInteract(this.player, blockpos)) {
                            InteractionResult interactionresult = this.player.gameMode.useItemOn(this.player, serverlevel, itemstack, interactionhand, blockhitresult);
                            if (direction == Direction.UP && !interactionresult.consumesAction() && blockpos.getY() >= i - 1 && wasBlockPlacementAttempt(this.player, itemstack)) {
                                Component component = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED);
                                this.player.sendSystemMessage(component, true);
                            } else if (interactionresult.shouldSwing()) {
                                this.player.swing(interactionhand, true);
                            }
                        }
                    } else {
                        Component component1 = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED);
                        this.player.sendSystemMessage(component1, true);
                    }                    this.player.connection.send(new ClientboundBlockUpdatePacket(serverlevel, blockpos));
                    this.player.connection.send(new ClientboundBlockUpdatePacket(serverlevel, blockpos.relative(direction)));
                } else {
                    LOGGER.warn("Rejecting UseItemOnPacket from {}: Location {} too far away from hit block {}.", this.player.getGameProfile().getName(), vec3, blockpos);
                }
            }
        }
    }
    ...
}

As seen in the class ServerGamePacketListenerImpl, we see that there's only functionality for the max height warning indicator, not the min world height. 

Fix:

To fix this, all we have to do is to add compatibility for min world height. Really, it's just copy and pasting with a couple more modifications.

net.minecraft.server.network.ServerGamePacketListenerImpl.java // Updated

public class ServerGamePacketListenerImpl implements ServerPlayerConnection, TickablePacketListener, ServerGamePacketListener {
    ...
    public void handleUseItemOn(ServerboundUseItemOnPacket p_9930_) {
        PacketUtils.ensureRunningOnSameThread(p_9930_, this, this.player.serverLevel());
        this.player.connection.ackBlockChangesUpTo(p_9930_.getSequence());
        ServerLevel serverlevel = this.player.serverLevel();
        InteractionHand interactionhand = p_9930_.getHand();
        ItemStack itemstack = this.player.getItemInHand(interactionhand);
        if (itemstack.isItemEnabled(serverlevel.enabledFeatures())) {
            BlockHitResult blockhitresult = p_9930_.getHitResult();
            Vec3 vec3 = blockhitresult.getLocation();
            BlockPos blockpos = blockhitresult.getBlockPos();
            Vec3 vec31 = Vec3.atCenterOf(blockpos);
            if (!(this.player.getEyePosition().distanceToSqr(vec31) > MAX_INTERACTION_DISTANCE)) {
                Vec3 vec32 = vec3.subtract(vec31);
                double d0 = 1.0000001 D;
                if (Math.abs(vec32.x()) < 1.0000001 D && Math.abs(vec32.y()) < 1.0000001 D && Math.abs(vec32.z()) < 1.0000001 D) {
                    Direction direction = blockhitresult.getDirection();
                    this.player.resetLastActionTime();
                    int i = this.player.level().getMaxBuildHeight();
                    int j = this.player.level().getMinBuildHeight();                    if (blockpos.getY() < i && blockpos.getY() >= j) {
                        if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockpos.getX() + 0.5 D, (double) blockpos.getY() + 0.5 D, (double) blockpos.getZ() + 0.5 D) < 64.0 D && serverlevel.mayInteract(this.player, blockpos)) {
                            InteractionResult interactionresult = this.player.gameMode.useItemOn(this.player, serverlevel, itemstack, interactionhand, blockhitresult);
                            if (direction == Direction.UP && !interactionresult.consumesAction() && blockpos.getY() >= i - 1 && wasBlockPlacementAttempt(this.player, itemstack)) {
                                Component component = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED);
                                this.player.sendSystemMessage(component, true);
                            } else if (direction == Direction.DOWN && !interactionresult.consumesAction() && blockpos.getY() <= j && wasBlockPlacementAttempt(this.player, itemstack)) {
                                Component component = Component.translatable("build.tooLower", j + 1).withStyle(ChatFormatting.RED);
                                this.player.sendSystemMessage(component, true);
                            } else if (interactionresult.shouldSwing()) {
                                this.player.swing(interactionhand, true);
                            }
                        }
                    } else {
                        if (direction == Direction.UP) {
                            Component component1 = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED);
                            this.player.sendSystemMessage(component1, true);
                        } else if (direction == Direction.DOWN) {
                            Component component1 = Component.translatable("build.tooLower", j + 1).withStyle(ChatFormatting.RED);
                            this.player.sendSystemMessage(component1, true);
                        }
                    }                    this.player.connection.send(new ClientboundBlockUpdatePacket(serverlevel, blockpos));
                    this.player.connection.send(new ClientboundBlockUpdatePacket(serverlevel, blockpos.relative(direction)));
                } else {
                    LOGGER.warn("Rejecting UseItemOnPacket from {}: Location {} too far away from hit block {}.", this.player.getGameProfile().getName(), vec3, blockpos);
                }
            }
        }
    }
    ...
}

A fair warning though, this may be a solution to indicate a lower build limit warning, you will need to add a translatable to en_us.json for the key: build.toLower.

As an example:
"build.tooLower": "Height limit for building is %s",

muzikbike

(Unassigned)

Confirmed

Platform

Low

UI

build-limit, ui, vanilla-parity, void, y-coordinate

Minecraft 1.12.2, Minecraft 18w20c, Minecraft 18w21a, Minecraft 18w21b, Minecraft 18w22a, ..., 1.19.4, 23w16a, 1.20 Pre-release 1, 1.20.1, 1.20.4

Retrieved