回弹

    From Minecraft Parkour Wiki
    This page is a translated version of the page Lagback and the translation is 100% complete.
    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修复),跳跃重置玩家的坠落距离,这使回弹可以在高版本被利用在高坠落时幸存下来。