When a player breaks a block mostly on the ground but finishes the block breaking in mid air, the block will reappear for short amount of time. In versions before 26.3 it would reappear until the player hits the ground again, but this doesn’t seem to be the case anymore in 26.3+ and the block instead disappears again instantly.
Reproduction:
This bug is a lot easier to achieve by using low gravity, I used the following two commands in my attached video:
/attribute @s minecraft:gravity base set 0.005
/attribute @s minecraft:jump_strength base set 0.15
Simply start breaking a block and jump right before it is fully destroyed and keep breaking the block until it disappears.
This can also be done with tick rate 1, clearly showing the block reappearing for a tick after breaking.
Notes:
This seems to happen due to the fact that the server doesn’t precisely track the players destroy progress, but instead just checks if the player has been destroying the block for enough ticks once the client sends the corresponding packet to destroy the block. This calculation to check if the player was destroying the block for long enough just uses the players position at that moment to determine if the mid-air destroying speed penalty should be applied for the entire duration of the destroying phase.
The following are the relevant code snippets from ServerPlayerGameMode::handleBlockBreakAction (in the branch for the stop destroy block action) and methods that are called from there:
if (pos.equals(this.destroyPos)) {
int ticksSpentDestroying = this.gameTicks - this.destroyProgressStart;
BlockState state = this.level.getBlockState(pos);
if (!state.isAir()) {
float destroyProgress = state.getDestroyProgress(this.player, this.player.level(), pos) * (ticksSpentDestroying + 1);
if (destroyProgress >= 0.7F) {
this.isDestroyingBlock = false;
this.level.destroyBlockProgress(this.player.getId(), pos, -1);
this.destroyAndAck(pos, sequence, "destroyed");
return;
}
[...]BlockBehaviour::getDestroyProgress
protected float getDestroyProgress(final BlockState state, final Player player, final BlockGetter level, final BlockPos pos) {
float destroySpeed = state.getDestroySpeed(level, pos);
if (destroySpeed == -1.0F) {
return 0.0F;
}
int modifier = player.hasCorrectToolForDrops(state) ? 30 : 100;
return player.getDestroySpeed(state) / destroySpeed / modifier;
}and finally Player::getDestroySpeed
public float getDestroySpeed(final BlockState state) {
float speed = this.inventory.getSelectedItem().getDestroySpeed(state);
[...]
if (!this.onGround()) {
speed /= 5.0F;
}
return speed;
}So effectively the server only cares about whether the player is in mid-air at the exact moment of breaking the block and will assume that the destroying of the block should take almost 5x the amount of time than the player has taken to break it on the client.
I believe it would be very reasonable to have an instance field destroyProgress of ServerPlayerGameMode which keeps track of the current destroyProgress and increments it while breaking a block, instead of fully recalculating the destroy progress inaccurately on every tick.
Thank you for helping us improve Minecraft! We saved your files: