回弹
回弹是指玩家被传送回之前的位置。它可以被利用于卡进墙壁或以类似卡角的方式跳得更高,尽管这些做法在多人服务器上可能是禁止的。在本文中,我们主要关注1.8的单人模式,但大多数信息都与多人模式和1.9+相关。
以下是已知的回弹触发因素:
常规解释
触发回弹有两种方法:
- 与碰撞箱相交(卡在方块中,或与船相撞)。
- "错误地"移动。
第一种方法是无需解释的。
关于第二种方法,我们必须首先了解玩家的移动是如何在服务端处理的(即使是在单人游戏中,游戏也运行在一个集成的服务器上)。每过一刻,客户端就会计算玩家的下一个位置,并向服务器发送一个数据包。服务器通过从玩家的最后一个位置重新计算来验证玩家的移动,理论上两者计算结果应相同。然而,服务器不会消除玩家移动的副作用,这可能会导致玩家预期和实际位置之间的差异。如果两者之间的水平距离超过 0.25 m ,那么这种差异就会被认为是显著的,在这种情况下,玩家会被回弹至他们之前的位置。
在验证之前未消除的副作用包括:
onGround
着陆时被设置为true
(这会改变潜行的行为)。inWeb
在与蜘蛛网碰撞时被设置为false
。- 当与墙壁或天花板发生碰撞时,各种
collided
被设置为true
。
总之,玩家的移动每刻计算两次,但第一次计算的副作用可能会影响第二次计算的结果。如果玩家移动足够快,服务器就会认为玩家移动错误,从而触发回弹。
特殊触发的解释
与碰撞箱相交
The player is not supposed to move into a collision box. However, the server has a leniency of 0.0625b in each direction, meaning the player's bounding box can intersect with the "exterior shell" of a block and move normally. If the player move further inward, it triggers a lagback.
If the player is already fully inside a collision box, they do not get lagged back when moving.
Colliding with a cobweb
When colliding with a cobweb (detected at the end of the tick, but at the player's previous position), the player's inWeb
flag is set to true
. When the player is moved and and inWeb
is true
, it is immediately set to false
, and the cobweb's effect is applied on the player's velocity. This causes a discrepancy between the original calculation and the recalculation, which triggers a lagback if sufficient speed is met.
Landing from a high fall while sneaking
This variant is achieved by landing and sneaking with the following requirements:
- Moving horizontally at strictly more than 0.25m/t.
- Being at least one block above ground on the tick before landing (or 0.6b in 1.11+).
The minimum jump height required to achieve at least -1m/t of vertical speed is -7.3125b. An easy setup to try this method is -9.5b. You are not required to time the sneak exactly, but sneaking too early will significantly reduce the player's horizontal speed.
Sneak Glitch
A sneak glitch (or shift glitch) happens when the player sneaks while landing on an edge, which causes them to fall anyway (this was fixed in 1.16.2).
When performing a sneak glitch, the server's expected movement for the player is different from their actual movement. Here's why:
- During the original calculation, the player is sneaking but not "on ground", hence they move past the edge. However, they still land, which sets
onGround = true
. - During the recalculation, the player is now considered "on ground" before actually landing, which changes the behaviour of sneaking: they can not "fall off the edge", despite not being at ground level.
With at least 0.25m/t of horizontal speed and appropriate placement, performing a sneak glitch can therefore trigger a lagback. After being teleported back to their previous position, the player is now able to jump. The resulting jump looks similar to a blip-up, in the sense that the player can start jumping mid-air. Here is an example:
Some jump heights do not work. This is still being investigated.
Code
//in NetHandlerPlayServer.java
public void processPlayer(PacketPlayer packetIn)
{
double posY_original = this.playerEntity.posY;
this.lastPosX = this.playerEntity.posX;
this.lastPosY = this.playerEntity.posY;
this.lastPosZ = this.playerEntity.posZ;
double next_posX = packetIn.getPositionX();
double next_posY = packetIn.getPositionY();
double next_posZ = packetIn.getPositionZ();
float yaw = this.playerEntity.rotationYaw;
float pitch = this.playerEntity.rotationPitch;
if (packetIn.getRotating()) {yaw = packetIn.getYaw(); pitch = packetIn.getPitch();}
</div>
<div lang="en" dir="ltr" class="mw-content-ltr">
//checks whether the player is starting from inside a collision box: in this case, there is no lagback
boolean noCollisionInside = worldserver.getCollidingBoundingBoxes(this.playerEntity, this.playerEntity.getEntityBoundingBox().contract(0.0625, 0.0625, 0.0625).isEmpty();
</div>
<div lang="en" dir="ltr" class="mw-content-ltr">
//begin movement verification
double deltaX = next_posX - this.playerEntity.posX;
double deltaY = next_posY - this.playerEntity.posY;
double deltaZ = next_posZ - this.playerEntity.posZ;
</div>
<div lang="en" dir="ltr" class="mw-content-ltr">
if (this.playerEntity.onGround && !packetIn.isOnGround() && deltaY > 0.0)
this.playerEntity.jump();
this.playerEntity.moveEntity(deltaX, deltaY, deltaZ);
this.playerEntity.onGround = packetIn.isOnGround();
//calculates error between the expected and actual positions
double errorX = next_posX - this.playerEntity.posX;
double errorZ = next_posZ - this.playerEntity.posZ;
double error_squared = errorX * errorX + errorZ * errorZ;
boolean movedWrongly = (error_squared > 0.0625 && !this.playerEntity.theItemInWorldManager.isCreative());
</div>
<div lang="en" dir="ltr" class="mw-content-ltr">
//sets the player to their actual position, and checks for collisions
this.playerEntity.setPositionAndRotation(next_posX, next_posY, next_posZ, yaw, pitch);
boolean next_noCollisionInside = worldserver.getCollidingBoundingBoxes(this.playerEntity, this.playerEntity.getEntityBoundingBox().contract(0.0625, 0.0625, 0.0625).isEmpty();
</div>
<div lang="en" dir="ltr" class="mw-content-ltr">
//detects an error: player moved wrongly, or moved inside a collision box
if (noCollisionInside && (movedWrongly || !next_noCollisionInside))
{
//lags the player back to their previous position
this.setPlayerLocation(this.lastPosX, this.lastPosY, this.lastPosZ, yaw, pitch);
return;
}
</div>
<div lang="en" dir="ltr" class="mw-content-ltr">
this.playerEntity.handleFalling(this.playerEntity.posY - posY_original, packetIn.isOnGround());
}