Lagback/zh: Difference between revisions

From Minecraft Parkour Wiki
Content added Content deleted
(Created page with "此变种是通过掉落和潜行实现的,具有以下要求:")
(Updating to match new version of source page)
 
(28 intermediate revisions by 2 users not shown)
Line 1: Line 1:
<languages/>
<languages/>
回弹是指玩家被传送回之前的位置。它可以被利用于卡进墙壁或以类似[[Special:MyLanguage/Blip|卡角]]的方式跳得更高,尽管这些做法在多人服务器上可能是禁止的。在本文中,我们主要关注1.8的单人模式,但大多数信息都与多人模式和1.9+相关。
回弹是指玩家被传送回之前的位置。它可以被利用于卡进墙壁或以类似[[Special:MyLanguage/Blip|卡角]]的方式跳得更高,尽管这些做法在多人服务器上可能是禁止的。在本文中,我们主要关注 1.8 的单人模式,但大多数信息都与多人模式和 1.9+ 相关。


以下是已知的回弹触发因素:
以下是已知的回弹触发因素:
Line 7: Line 7:
* 在[[Special:MyLanguage/cobweb|蜘蛛网]]中移动过快。
* 在[[Special:MyLanguage/cobweb|蜘蛛网]]中移动过快。
* 以足够的速度在[[Special:MyLanguage/sneaking|潜行]]时降落
* 以足够的速度在[[Special:MyLanguage/sneaking|潜行]]时降落
* 以极快的速度(例如速度100)通过[[Special:MyLanguage/Stepping|步行辅助]]移动。
* 以极快的速度例如速度 100)通过[[Special:MyLanguage/Stepping|步行辅助]]移动。
* 靠着墙快速移动。
* 靠着墙快速移动。




<span id="General_Explanation"></span>
== 常规解释 ==
== 常规解释 ==


触发回弹有两种方法:
触发回弹有两种方法:
* 与碰撞箱相交(卡在方块中,或与船相撞)。
* 与碰撞箱相交(卡在方块中,或与船相撞)。
* "''错误地''"移动。
* ''错误地''移动。
第一种方法是无需解释的。
第一种方法是无需解释的。


Line 29: Line 30:




<span id="Explanations_for_specific_triggers"></span>
== 特殊触发的解释 ==
== 特殊触发的解释 ==




<span id="Intersecting_with_a_collision_box"></span>
=== 与碰撞箱相交 ===
=== 与碰撞箱相交 ===


Line 39: Line 42:




<span id="Colliding_with_a_cobweb_(WIP)"></span>
=== 与蜘蛛网碰撞 ===
=== 与蜘蛛网碰撞(WIP) ===

当与[[Special:MyLanguage/cobweb|蜘蛛网]]碰撞时(在滴答结束时检测,但在玩家的''上一个''位置),玩家的 <code>inWeb</code> 被设置为 <code>true</code>。当玩家移动并且 <code>inWeb</code> 为 <code>true</code> 时,它会立即被设置为 <code>false</code>,并且蜘蛛网的效果会应用在玩家的速度上。这会导致原始计算和重新计算之间存在差异,如果满足足够的速度,则会触发回弹。


当与[[Special:MyLanguage/cobweb|蜘蛛网]]碰撞时(在每刻结束时检测),玩家的 <code>inWeb</code> 被设置为 <code>true</code>。当玩家移动并且 <code>inWeb</code> 为 <code>true</code> 时,它会立即被设置为 <code>false</code>,并且蜘蛛网的效果会应用在玩家的速度上。这会导致原始计算和重新计算之间存在差异,如果满足足够的速度,则会触发回弹。


<span id="Landing_from_a_high_fall_while_sneaking"></span>
=== 在潜行时从高处落下 ===
=== 在潜行时从高处落下 ===


此变种是通过掉落和潜行实现的,具有以下要求:
此变种是通过掉落和潜行实现的,具有以下要求:


* 水平移动速度超过 0.25m/t。
<div lang="en" dir="ltr" class="mw-content-ltr">
* 落地前一刻至少在地面上方一格(在 1.11+ 中为 0.6 格)。
* 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+).
你不需要准确地计算潜行时间,但过早潜行会显著降低玩家的水平速度。
</div>

达到至少 -1m/t 垂直速度所需的最小跳跃高度为 -7.3125b。尝试这种方法的一个简单高度是 -9.5b。这是一个例子:


<youtube>https://youtu.be/-fABDG3YVpw</youtube>
<div lang="en" dir="ltr" class="mw-content-ltr">
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.
</div>




<span id="Sneak_Glitch"></span>
<div lang="en" dir="ltr" class="mw-content-ltr">
=== Sneak Glitch ===
=== 潜行故障 ===
</div>


当玩家[[Special:MyLanguage/Sneaking|潜行]]时落在方块边缘会发生潜行故障(或下蹲故障),导致他们掉落(在 1.16.2 中被修复)。
<div lang="en" dir="ltr" class="mw-content-ltr">
A sneak glitch (or shift glitch) happens when the player [[Special:MyLanguage/Sneaking|sneaks]] while landing on an edge, which causes them to fall anyway (this was fixed in 1.16.2).
</div>


执行潜行故障时,服务器对玩家的预期移动与玩家的实际移动不同。原因如下:
<div lang="en" dir="ltr" class="mw-content-ltr">
When performing a sneak glitch, the server's expected movement for the player is different from their actual movement. Here's why:
</div>


* 在最初的计算过程中,玩家是在潜行,但并不“在地面上”,因此他们会走出方块边缘。但是,之后玩家仍然会落地,并设置 <code>onGround = true</code>。
<div lang="en" dir="ltr" class="mw-content-ltr">
* 在重新计算期间,玩家现在在实际落地之前被认为是“在地面上”,这改变了潜行的行为:他们不能“从边缘掉落”,尽管不在地面上。
* During the original calculation, the player is sneaking but not "on ground", hence they move past the edge. However, they still land, which sets <code>onGround = true</code>.
* 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.
</div>


以至少 0.25m/t 的水平速度和适当的位置,执行潜行故障,则会引发回弹。在被传送回之前的位置后,玩家可以跳跃。由此产生的跳跃看起来类似于[[Special:MyLanguage/Blip|卡角]],即玩家可以在半空中开始跳跃。下面是一个例子:
<div lang="en" dir="ltr" class="mw-content-ltr">
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 [[Special:MyLanguage/Blip|blip-up]], in the sense that the player can start jumping mid-air. Here is an example:
</div>


<div lang="en" dir="ltr" class="mw-content-ltr">
<youtube>https://youtu.be/IOrpHNqn5t0</youtube>
<youtube>https://youtu.be/IOrpHNqn5t0</youtube>
</div>


一些跳跃高度没有作用。这仍在研究中。
<div lang="en" dir="ltr" class="mw-content-ltr">
Some jump heights do not work. This is still being investigated.
</div>




<span id="Code"></span>
<div lang="en" dir="ltr" class="mw-content-ltr">
== Code ==
== 代码 ==
</div>


在单人游戏中,回弹是由这部分代码引起的(高度简化,我们忽略了飞行、睡眠、传送、重生、加载实体等异常情况......):<syntaxhighlight lang="java" line="1">
<div lang="en" dir="ltr" class="mw-content-ltr">
//位于 NetHandlerPlayServer.java
In singleplayer, lagbacks are caused by this section of code (heavily simplified, we ignore exceptional cases such as flying, sleeping, teleporting, respawning, mounting an entity...):<syntaxhighlight lang="java" line="1">
//in NetHandlerPlayServer.java
public void processPlayer(PacketPlayer packetIn)
public void processPlayer(PacketPlayer packetIn)
{
{
Line 107: Line 96:
float pitch = this.playerEntity.rotationPitch;
float pitch = this.playerEntity.rotationPitch;
if (packetIn.getRotating()) {yaw = packetIn.getYaw(); pitch = packetIn.getPitch();}
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();
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 deltaX = next_posX - this.playerEntity.posX;
double deltaY = next_posY - this.playerEntity.posY;
double deltaY = next_posY - this.playerEntity.posY;
double deltaZ = next_posZ - this.playerEntity.posZ;
double deltaZ = next_posZ - this.playerEntity.posZ;
</div>


if (this.playerEntity.onGround && !packetIn.isOnGround() && deltaY > 0.0)
<div lang="en" dir="ltr" class="mw-content-ltr">
if (this.playerEntity.onGround && !packetIn.isOnGround() && deltaY > 0.0)
this.playerEntity.jump();
this.playerEntity.jump();
this.playerEntity.moveEntity(deltaX, deltaY, deltaZ);
this.playerEntity.moveEntity(deltaX, deltaY, deltaZ);
this.playerEntity.onGround = packetIn.isOnGround();
this.playerEntity.onGround = packetIn.isOnGround();
//计算预期位置和实际位置之间的误差
//calculates error between the expected and actual positions
double errorX = next_posX - this.playerEntity.posX;
double errorX = next_posX - this.playerEntity.posX;
double errorZ = next_posZ - this.playerEntity.posZ;
double errorZ = next_posZ - this.playerEntity.posZ;
double error_squared = errorX * errorX + errorZ * errorZ;
double error_squared = errorX * errorX + errorZ * errorZ;
boolean movedWrongly = (error_squared > 0.0625 && !this.playerEntity.theItemInWorldManager.isCreative());
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);
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();
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))
if (noCollisionInside && (movedWrongly || !next_noCollisionInside))
{
{
//让玩家回到之前的位置
//lags the player back to their previous position
this.setPlayerLocation(this.lastPosX, this.lastPosY, this.lastPosZ, yaw, pitch);
this.setPlayerLocation(this.lastPosX, this.lastPosY, this.lastPosZ, yaw, pitch);
return;
return;
}
}
</div>


this.playerEntity.handleFalling(this.playerEntity.posY - posY_original, packetIn.isOnGround());
<div lang="en" dir="ltr" class="mw-content-ltr">
this.playerEntity.handleFalling(this.playerEntity.posY - posY_original, packetIn.isOnGround());
}
}
</syntaxhighlight>需要注意的是,玩家回弹时,掉落伤害不会被处理。这可能会导致玩家在最终落地时遭受意外的高坠落伤害。自1.15以来(被当作[https://bugs.mojang.com/browse/MC-153698 船掉落伤害bug]修复),跳跃重置玩家的坠落距离,这使回弹可以在高版本被利用在高坠落时幸存下来。
</syntaxhighlight>Note that the handling of fall damage is not processed if the player gets lagbacked. This may cause the player to suffer unexpectedly high fall damage when they eventually land. Since 1.15 (when [https://bugs.mojang.com/browse/MC-153698 the boat fall damage bug] was fixed), jumping resets the player's fall distance, which can be abused to survive high falls when combined with lagback.
</div>

Latest revision as of 12:57, 3 February 2023

Other languages:

回弹是指玩家被传送回之前的位置。它可以被利用于卡进墙壁或以类似卡角的方式跳得更高,尽管这些做法在多人服务器上可能是禁止的。在本文中,我们主要关注 1.8 的单人模式,但大多数信息都与多人模式和 1.9+ 相关。

以下是已知的回弹触发因素:

  • 卡在一个方块中。
  • 与船相撞。
  • 蜘蛛网中移动过快。
  • 以足够的速度在潜行时降落
  • 以极快的速度(例如速度 100)通过步行辅助移动。
  • 靠着墙快速移动。


常规解释

触发回弹有两种方法:

  • 与碰撞箱相交(卡在方块中,或与船相撞)。
  • 错误地”移动。

第一种方法是无需解释的。

关于第二种方法,我们必须首先了解玩家的移动是如何在服务端处理的(即使是在单人游戏中,游戏也运行在一个集成的服务器上)。每过一刻,客户端就会计算玩家的下一个位置,并向服务器发送一个数据包。服务器通过从玩家的最后一个位置重新计算来验证玩家的移动,理论上两者计算结果应相同。然而,服务器不会消除玩家移动的副作用,这可能会导致玩家预期和实际位置之间的差异。如果两者之间的水平距离超过 0.25 m ,那么这种差异就会被认为是显著的,在这种情况下,玩家会被回弹至他们之前的位置。

在验证之前未消除的副作用包括:

  • onGround 着陆时被设置为 true (这会改变潜行的行为)。
  • inWeb 在与蜘蛛网碰撞时被设置为 false
  • 当与墙壁或天花板发生碰撞时,各种 collided 被设置为 true

总之,玩家的移动每刻计算两次,但第一次计算的副作用可能会影响第二次计算的结果。如果玩家移动足够快,服务器就会认为玩家移动错误,从而触发回弹。


特殊触发的解释

与碰撞箱相交

玩家不应该移动到一个碰撞箱中。但是,服务器在每个方向都有 0.0625b 的宽容度,这意味着玩家的边界箱可以与方块的"表面"相交并正常移动。如果玩家进一步向内移动,就会触发回弹。

如果玩家已经完全进入方块的碰撞箱,则玩家在移动时不会回弹。


与蜘蛛网碰撞(WIP)

当与蜘蛛网碰撞时(在每刻结束时检测),玩家的 inWeb 被设置为 true。当玩家移动并且 inWebtrue 时,它会立即被设置为 false,并且蜘蛛网的效果会应用在玩家的速度上。这会导致原始计算和重新计算之间存在差异,如果满足足够的速度,则会触发回弹。

在潜行时从高处落下

此变种是通过掉落和潜行实现的,具有以下要求:

  • 水平移动速度超过 0.25m/t。
  • 落地前一刻至少在地面上方一格(在 1.11+ 中为 0.6 格)。

你不需要准确地计算潜行时间,但过早潜行会显著降低玩家的水平速度。

达到至少 -1m/t 垂直速度所需的最小跳跃高度为 -7.3125b。尝试这种方法的一个简单高度是 -9.5b。这是一个例子:


潜行故障

当玩家潜行时落在方块边缘会发生潜行故障(或下蹲故障),导致他们掉落(在 1.16.2 中被修复)。

执行潜行故障时,服务器对玩家的预期移动与玩家的实际移动不同。原因如下:

  • 在最初的计算过程中,玩家是在潜行,但并不“在地面上”,因此他们会走出方块边缘。但是,之后玩家仍然会落地,并设置 onGround = true
  • 在重新计算期间,玩家现在在实际落地之前被认为是“在地面上”,这改变了潜行的行为:他们不能“从边缘掉落”,尽管不在地面上。

以至少 0.25m/t 的水平速度和适当的位置,执行潜行故障,则会引发回弹。在被传送回之前的位置后,玩家可以跳跃。由此产生的跳跃看起来类似于卡角,即玩家可以在半空中开始跳跃。下面是一个例子:

一些跳跃高度没有作用。这仍在研究中。


代码

在单人游戏中,回弹是由这部分代码引起的(高度简化,我们忽略了飞行、睡眠、传送、重生、加载实体等异常情况......):

//位于 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();}

    //检查玩家是否在碰撞箱内开始:这种情况下不会回弹
    boolean noCollisionInside = worldserver.getCollidingBoundingBoxes(this.playerEntity, this.playerEntity.getEntityBoundingBox().contract(0.0625, 0.0625, 0.0625).isEmpty();

    //开始运动验证
    double deltaX = next_posX - this.playerEntity.posX;
    double deltaY = next_posY - this.playerEntity.posY;
    double deltaZ = next_posZ - this.playerEntity.posZ;

    if (this.playerEntity.onGround && !packetIn.isOnGround() && deltaY > 0.0)
        this.playerEntity.jump();
    this.playerEntity.moveEntity(deltaX, deltaY, deltaZ);
    this.playerEntity.onGround = packetIn.isOnGround();
    
    //计算预期位置和实际位置之间的误差
    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());

    //将玩家设置到他们的实际位置,并检查碰撞
    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();

    //检测到错误:玩家的移动与预期不符,或卡进了碰撞箱
    if (noCollisionInside && (movedWrongly || !next_noCollisionInside))
    {
        //让玩家回到之前的位置
        this.setPlayerLocation(this.lastPosX, this.lastPosY, this.lastPosZ, yaw, pitch);
        return;
    }

    this.playerEntity.handleFalling(this.playerEntity.posY - posY_original, packetIn.isOnGround());
}

需要注意的是,玩家回弹时,掉落伤害不会被处理。这可能会导致玩家在最终落地时遭受意外的高坠落伤害。自1.15以来(被当作船掉落伤害bug修复),跳跃重置玩家的坠落距离,这使回弹可以在高版本被利用在高坠落时幸存下来。