Lagback/zh: Difference between revisions

From Minecraft Parkour Wiki
Content added Content deleted
No edit summary
(Updating to match new version of source page)
 
(7 intermediate revisions by one other user 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>
=== 在潜行时从高处落下 ===
=== 在潜行时从高处落下 ===


Line 58: Line 62:




<span id="Sneak_Glitch"></span>
=== 潜行故障 ===
=== 潜行故障 ===


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


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


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




<span id="Code"></span>
== 代码 ==
== 代码 ==


在单人游戏中,回弹是由这部分代码引起的(高度简化,我们忽略了飞行、睡眠、传送、重生、加载实体等异常情况......):<syntaxhighlight lang="java" line="1">
在单人游戏中,回弹是由这部分代码引起的(高度简化,我们忽略了飞行、睡眠、传送、重生、加载实体等异常情况......):<syntaxhighlight lang="java" line="1">
//in NetHandlerPlayServer.java
//位于 NetHandlerPlayServer.java
public void processPlayer(PacketPlayer packetIn)
public void processPlayer(PacketPlayer packetIn)
{
{
Line 114: Line 120:
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();


//检测到错误:玩家的移动与预期不符,或卡进了碰撞箱
//detects an error: player moved wrongly, or moved inside a collision box
if (noCollisionInside && (movedWrongly || !next_noCollisionInside))
if (noCollisionInside && (movedWrongly || !next_noCollisionInside))
{
{

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修復),跳躍重置玩家的墜落距離,這使回彈可以在高版本被利用在高墜落時倖存下來。