Search in sources :

Example 1 with LiftOffEnvelope

use of fr.neatmonster.nocheatplus.checks.moving.model.LiftOffEnvelope in project NoCheatPlus by NoCheatPlus.

the class SurvivalFly method check.

/**
 * @param player
 * @param from
 * @param to
 * @param multiMoveCount
 *            =: Ordinary, 1/2: first/second of a split move.
 * @param data
 * @param cc
 * @param tick
 * @param now
 * @param useBlockChangeTracker
 * @return
 */
public Location check(final Player player, final PlayerLocation from, final PlayerLocation to, final int multiMoveCount, final MovingData data, final MovingConfig cc, final IPlayerData pData, final int tick, final long now, final boolean useBlockChangeTracker) {
    tags.clear();
    final boolean debug = pData.isDebugActive(type);
    if (debug) {
        justUsedWorkarounds.clear();
        data.ws.setJustUsedIds(justUsedWorkarounds);
    }
    final PlayerMoveData thisMove = data.playerMoves.getCurrentMove();
    final PlayerMoveData lastMove = data.playerMoves.getFirstPastMove();
    final boolean isSamePos = from.isSamePos(to);
    // Calculate some distances.
    final double xDistance, yDistance, zDistance, hDistance;
    final boolean hasHdist;
    if (isSamePos) {
        // TODO: Could run a completely different check here (roughly none :p).
        xDistance = yDistance = zDistance = hDistance = 0.0;
        hasHdist = false;
    } else {
        xDistance = to.getX() - from.getX();
        yDistance = thisMove.yDistance;
        zDistance = to.getZ() - from.getZ();
        if (xDistance == 0.0 && zDistance == 0.0) {
            hDistance = 0.0;
            hasHdist = false;
        } else {
            hasHdist = true;
            hDistance = thisMove.hDistance;
        }
    }
    // Recover from data removal (somewhat random insertion point).
    if (data.liftOffEnvelope == LiftOffEnvelope.UNKNOWN) {
        data.adjustMediumProperties(from);
    }
    // Set some flags.
    final boolean fromOnGround = thisMove.from.onGround;
    // TODO: Work in the past ground stuff differently (thisMove, touchedGround?, from/to ...)
    final boolean toOnGround = thisMove.to.onGround || useBlockChangeTracker && toOnGroundPastStates(from, to, thisMove, tick, data, cc);
    final boolean resetTo = toOnGround || to.isResetCond();
    // Determine if the player is actually sprinting.
    final boolean sprinting;
    if (data.lostSprintCount > 0) {
        // NOTE: This could extend the "sprinting grace" period, theoretically, until on ground.
        if (resetTo && (fromOnGround || from.isResetCond()) || hDistance <= Magic.WALK_SPEED) {
            // Invalidate.
            data.lostSprintCount = 0;
            tags.add("invalidate_lostsprint");
            if (now <= data.timeSprinting + cc.sprintingGrace) {
                sprinting = true;
            } else {
                sprinting = false;
            }
        } else {
            tags.add("lostsprint");
            sprinting = true;
            if (data.lostSprintCount < 3 && toOnGround || to.isResetCond()) {
                data.lostSprintCount = 0;
            } else {
                data.lostSprintCount--;
            }
        }
    } else if (now <= data.timeSprinting + cc.sprintingGrace) {
        // Within grace period for hunger level being too low for sprinting on server side (latency).
        if (now != data.timeSprinting) {
            tags.add("sprintgrace");
        }
        sprinting = true;
    } else {
        sprinting = false;
    }
    // Use the player-specific walk speed.
    // TODO: Might get from listener.
    // TODO: Use in lostground?
    thisMove.walkSpeed = Magic.WALK_SPEED * ((double) data.walkSpeed / Magic.DEFAULT_WALKSPEED);
    setNextFriction(thisMove, data, cc);
    // ///////////////////////////////
    // Mixed checks (lost ground).
    // ///////////////////////////////
    final boolean resetFrom;
    if (fromOnGround || from.isResetCond()) {
        resetFrom = true;
    } else // TODO: Extra workarounds for toOnGround (step-up is a case with to on ground)?
    if (isSamePos) {
        // TODO: This isn't correct, needs redesign.
        if (useBlockChangeTracker && from.isOnGroundOpportune(cc.yOnGround, 0L, blockChangeTracker, data.blockChangeRef, tick)) {
            // TODO: Quick addition. Reconsider entry points etc.
            resetFrom = true;
            tags.add("pastground_from");
        } else if (lastMove.toIsValid) {
            // Note that to is not on ground either.
            resetFrom = LostGround.lostGroundStill(player, from, to, hDistance, yDistance, sprinting, lastMove, data, cc, tags);
        } else {
            resetFrom = false;
        }
    } else {
        // "Lost ground" workaround.
        // TODO: More refined conditions possible ?
        // TODO: Consider if (!resetTo) ?
        // Check lost-ground workarounds.
        resetFrom = LostGround.lostGround(player, from, to, hDistance, yDistance, sprinting, lastMove, data, cc, useBlockChangeTracker ? blockChangeTracker : null, tags);
    // Note: if not setting resetFrom, other places have to check assumeGround...
    }
    if (thisMove.touchedGround) {
        if (!thisMove.from.onGround && !thisMove.to.onGround) {
            // Lost ground workaround has just been applied, check resetting of the dirty flag.
            // TODO: Always/never reset with any ground touched?
            data.resetVelocityJumpPhase(tags);
        } else if (multiMoveCount == 0 && thisMove.from.onGround && !lastMove.touchedGround && TrigUtil.isSamePosAndLook(thisMove.from, lastMove.to)) {
            // Ground somehow appeared out of thin air (block place).
            data.setSetBack(from);
            if (debug) {
                debug(player, "Adjust set back on move: from is now on ground.");
            }
        }
    }
    // Renew the "dirty"-flag (in-air phase affected by velocity).
    if (data.isVelocityJumpPhase() || data.resetVelocityJumpPhase(tags)) {
        // (Reset is done after checks run.)
        tags.add("dirty");
    }
    // Check if head is obstructed.
    // if (!resetFrom || !resetTo) {
    thisMove.headObstructed = (yDistance > 0.0 ? from.isHeadObstructed(yDistance) : from.isHeadObstructed());
    // TODO: Consider setting on ground_height always?
    if ((from.getBlockFlags() & BlockProperties.F_ALLOW_LOWJUMP) != 0) {
        // TODO: Specialize - test for foot region?
        data.sfNoLowJump = true;
    }
    // ////////////////////
    // Horizontal move.
    // ////////////////////
    // TODO: Account for lift-off medium / if in air [i.e. account for medium + friction]?
    // Alter some data / flags.
    // TODO: Design to do the changing at the bottom? [if change: check limits in bunnyHop(...)]
    data.bunnyhopDelay--;
    // Set flag for swimming with the flowing direction of liquid.
    thisMove.downStream = hDistance > thisMove.walkSpeed * Magic.modSwim && thisMove.from.inLiquid && from.isDownStream(xDistance, zDistance);
    // TODO: Re-model ice stuff and other (e.g. general thing: ground-modifier + reset conditions).
    if (thisMove.from.onIce || thisMove.to.onIce) {
        // TODO: 1. Test if this can simply be removed. 2. Ensure data.sfOnIce resets with a violation.
        data.sfOnIce = 20;
    } else if (data.sfOnIce > 0) {
        // TODO: Here some friction might apply, could become a general thing with bunny and other.
        // TODO: Other reset conditions.
        data.sfOnIce--;
    }
    /*
         * TODO: if (Bridge1_9.isGlidingWithElytra(player)) { // Force stop
         * gliding?
         */
    // TODO: Remove these local variables ?
    double hAllowedDistance = 0.0, hDistanceAboveLimit = 0.0, hFreedom = 0.0;
    if (hasHdist) {
        // Check allowed vs. taken horizontal distance.
        // Get the allowed distance.
        hAllowedDistance = setAllowedhDist(player, sprinting, thisMove, data, cc, pData, false);
        // Judge if horizontal speed is above limit.
        hDistanceAboveLimit = hDistance - hAllowedDistance;
        // Velocity, buffers and after failure checks.
        if (hDistanceAboveLimit > 0) {
            // TODO: Move more of the workarounds (buffer, bunny, ...) into this method.
            final double[] res = hDistAfterFailure(player, from, to, hAllowedDistance, hDistanceAboveLimit, sprinting, thisMove, lastMove, data, cc, pData, false);
            hAllowedDistance = res[0];
            hDistanceAboveLimit = res[1];
            hFreedom = res[2];
        } else {
            data.clearActiveHorVel();
            hFreedom = 0.0;
            if (resetFrom && data.bunnyhopDelay <= 6) {
                data.bunnyhopDelay = 0;
            }
        }
        // hacc (if enabled, always update)
        final double fcmhv = Math.max(1.0, Math.min(10.0, thisMove.hDistance / thisMove.hAllowedDistanceBase));
        data.combinedMediumHCount++;
        data.combinedMediumHValue += fcmhv;
        // TODO: Balance, where to check / use (...).
        if (data.combinedMediumHCount > 30) {
            // TODO: Early trigger (> 0,1,2,5?), for way too high values. [in that case don't reset]
            final double fcmh = data.combinedMediumHValue / (double) data.combinedMediumHCount;
            final double limitFCMH;
            // TODO: with buffer use, might want to skip.
            if (data.liftOffEnvelope == LiftOffEnvelope.NORMAL) {
                limitFCMH = 1.34;
            } else if (data.liftOffEnvelope == LiftOffEnvelope.LIMIT_LIQUID || data.liftOffEnvelope == LiftOffEnvelope.LIMIT_NEAR_GROUND) {
                // limitFCMH = 1.05; // Seems to work on 1.10
                // 1.8.8 in-water moves with jumping near/on surface. 1.2 is max factor for one move (!).
                limitFCMH = 1.1;
            // TODO: Version+context dependent setting and/or confine by in-water moves, whatever.
            } else {
                limitFCMH = 1.0;
            }
            // TODO: Fly-NoFly + bunny-water transitions pose issues.
            if (fcmh > limitFCMH && !data.isVelocityJumpPhase()) {
                hDistanceAboveLimit = hDistance * (fcmh - limitFCMH);
                tags.add("hacc");
                // Reset for now.
                data.combinedMediumHCount = 0;
                data.combinedMediumHValue = 0.0;
            } else {
                // TODO: Other cases (1.0, between, ...)?
                data.combinedMediumHCount = 1;
                data.combinedMediumHValue = fcmhv;
            }
        }
        // TODO: Complete re-modeling.
        if (hDistanceAboveLimit <= 0D && hDistance > 0.1D && yDistance == 0D && !toOnGround && !fromOnGround && lastMove.toIsValid && lastMove.yDistance == 0D && BlockProperties.isLiquid(to.getTypeId()) && BlockProperties.isLiquid(from.getTypeId()) && !from.isHeadObstructed() && // TODO: Might decrease margin here.
        !to.isHeadObstructed()) {
            // TODO: Relative hdistance.
            // TODO: Might check actual bounds (collidesBlock). Might implement + use BlockProperties.getCorrectedBounds or getSomeHeight.
            hDistanceAboveLimit = Math.max(hDistanceAboveLimit, hDistance);
            tags.add("waterwalk");
        }
        // Prevent players from sprinting if they're moving backwards (allow buffers to cover up !?).
        if (sprinting && data.lostSprintCount == 0 && !cc.assumeSprint && hDistance > thisMove.walkSpeed && !data.hasActiveHorVel()) {
            // TODO: speed effects ?
            if (TrigUtil.isMovingBackwards(xDistance, zDistance, from.getYaw()) && !pData.hasPermission(Permissions.MOVING_SURVIVALFLY_SPRINTING, player)) {
                // (Might have to account for speeding permissions.)
                // TODO: hDistance is too harsh?
                hDistanceAboveLimit = Math.max(hDistanceAboveLimit, hDistance);
                // Might add it anyway.
                tags.add("sprintback");
            }
        }
    } else {
        /*
             * TODO: Consider to log and/or remember when this was last time
             * cleared [add time distance to tags/log on violations].
             */
        data.clearActiveHorVel();
        thisMove.hAllowedDistanceBase = 0.0;
        thisMove.hAllowedDistance = 0.0;
    // TODO: Other properties should be set as well?
    }
    // ////////////////////////
    // Vertical move.
    // ////////////////////////
    // Calculate the vertical speed limit based on the current jump phase.
    double vAllowedDistance = 0, vDistanceAboveLimit = 0;
    // Distinguish certain media.
    if (yDistance >= 0.0 && yDistance <= cc.sfStepHeight && toOnGround && fromOnGround) {
        // Wild-card allow step height from ground to ground.
        // TODO: Which of (fromOnGround || data.noFallAssumeGround || lastMove.toIsValid && lastMove.yDistance < 0.0)?
        vAllowedDistance = cc.sfStepHeight;
    } else if (from.isInWeb()) {
        // TODO: Further confine conditions.
        final double[] res = vDistWeb(player, thisMove, toOnGround, hDistanceAboveLimit, now, data, cc);
        vAllowedDistance = res[0];
        vDistanceAboveLimit = res[1];
        if (res[0] == Double.MIN_VALUE && res[1] == Double.MIN_VALUE) {
            // Silent set back.
            if (debug) {
                tags.add("silentsbcobweb");
                outputDebug(player, to, data, cc, hDistance, hAllowedDistance, hFreedom, yDistance, vAllowedDistance, fromOnGround, resetFrom, toOnGround, resetTo, thisMove);
                data.ws.setJustUsedIds(null);
            }
            // (OK)
            return data.getSetBack(to);
        }
    } else if (from.isOnClimbable()) {
        // Ladder types.
        vDistanceAboveLimit = vDistClimbable(player, from, to, fromOnGround, toOnGround, thisMove, lastMove, yDistance, data);
    } else if (thisMove.from.inLiquid) {
        // && (Math.abs(yDistance) > 0.2 || to.isInLiquid())) {
        // Swimming...
        final double[] res = vDistLiquid(from, to, toOnGround, yDistance, lastMove, data);
        vAllowedDistance = res[0];
        vDistanceAboveLimit = res[1];
        if (vDistanceAboveLimit <= 0.0 && yDistance > 0.0 && Math.abs(yDistance) > Magic.swimBaseSpeedV()) {
            data.setFrictionJumpPhase();
        }
    } else {
        final double[] res = vDistAir(now, player, from, fromOnGround, resetFrom, to, toOnGround, resetTo, hDistanceAboveLimit, yDistance, multiMoveCount, lastMove, data, cc, pData);
        vAllowedDistance = res[0];
        vDistanceAboveLimit = res[1];
    }
    // Post-check recovery.
    if (useBlockChangeTracker && vDistanceAboveLimit > 0.0) // Skip for now: && Math.abs(yDistance) <= 1.55
    {
        // TODO: Better place for checking for moved blocks [redesign for intermediate result objects?].
        // Vertical push/pull.
        double[] blockMoveResult = getVerticalBlockMoveResult(yDistance, from, to, tick, data);
        if (blockMoveResult != null) {
            vAllowedDistance = blockMoveResult[0];
            vDistanceAboveLimit = blockMoveResult[1];
        }
    }
    // Push/pull sideways.
    // TODO: Slightly itchy: regard x and z separately (Better in another spot).
    // TODO: on ground -> on ground improvements.
    // Debug output.
    final int tagsLength;
    if (debug) {
        outputDebug(player, to, data, cc, hDistance, hAllowedDistance, hFreedom, yDistance, vAllowedDistance, fromOnGround, resetFrom, toOnGround, resetTo, thisMove);
        tagsLength = tags.size();
        data.ws.setJustUsedIds(null);
    } else {
        // JIT vs. IDE.
        tagsLength = 0;
    }
    // /////////////////////
    // Handle violations.
    // /////////////////////
    final boolean inAir = Magic.inAir(thisMove);
    final double result = (Math.max(hDistanceAboveLimit, 0D) + Math.max(vDistanceAboveLimit, 0D)) * 100D;
    if (result > 0D) {
        final Location vLoc = handleViolation(now, result, player, from, to, data, cc);
        if (inAir) {
            data.sfVLInAir = true;
        }
        if (vLoc != null) {
            return vLoc;
        }
    } else {
        // TODO: Switch to move count instead of time (!).
        if (data.getPlayerMoveCount() - data.sfVLTime > cc.survivalFlyVLFreezeCount && (!cc.survivalFlyVLFreezeInAir || !inAir || // Favor bunny-hopping slightly: clean descend.
        !data.sfVLInAir && data.liftOffEnvelope == LiftOffEnvelope.NORMAL && lastMove.toIsValid && lastMove.yDistance < -Magic.GRAVITY_MIN && thisMove.yDistance - lastMove.yDistance < -Magic.GRAVITY_MIN)) {
            // Relax VL.
            data.survivalFlyVL *= 0.95;
            // Finally check horizontal buffer regain.
            if (hDistanceAboveLimit < 0.0 && result <= 0.0 && !isSamePos && data.sfHorizontalBuffer < cc.hBufMax) {
                // TODO: max min other conditions ?
                hBufRegain(hDistance, Math.min(0.2, Math.abs(hDistanceAboveLimit)), data, cc);
            }
        }
    }
    // ////////////////////////////////////////////////////////////////////////////////////////////
    // Set data for normal move or violation without cancel (cancel would have returned above).
    // ////////////////////////////////////////////////////////////////////////////////////////////
    // Check LiftOffEnvelope.
    // TODO: Web before liquid? Climbable?
    // TODO: isNextToGround(0.15, 0.4) allows a little much (yMargin), but reduces false positives.
    // TODO: nextToGround: Shortcut with block-flags ?
    final LiftOffEnvelope oldLiftOffEnvelope = data.liftOffEnvelope;
    if (to.isInLiquid()) {
        if (fromOnGround && !toOnGround && data.liftOffEnvelope == LiftOffEnvelope.NORMAL && data.sfJumpPhase <= 0 && !thisMove.from.inLiquid) {
        // KEEP
        } else if (to.isNextToGround(0.15, 0.4)) {
            // Consent with ground.
            data.liftOffEnvelope = LiftOffEnvelope.LIMIT_NEAR_GROUND;
        } else {
            // TODO: Distinguish strong limit from normal.
            data.liftOffEnvelope = LiftOffEnvelope.LIMIT_LIQUID;
        }
    } else if (thisMove.to.inWeb) {
        // TODO: Test.
        data.liftOffEnvelope = LiftOffEnvelope.NO_JUMP;
    } else if (resetTo) {
        // TODO: This might allow jumping on vines etc., but should do for the moment.
        data.liftOffEnvelope = LiftOffEnvelope.NORMAL;
    } else if (thisMove.from.inLiquid) {
        if (!resetTo && data.liftOffEnvelope == LiftOffEnvelope.NORMAL && data.sfJumpPhase <= 0) {
        // KEEP
        } else if (to.isNextToGround(0.15, 0.4)) {
            // TODO: Problematic: y-distance slope can be low jump.
            data.liftOffEnvelope = LiftOffEnvelope.LIMIT_NEAR_GROUND;
        } else {
            // TODO: Distinguish strong limit.
            data.liftOffEnvelope = LiftOffEnvelope.LIMIT_LIQUID;
        }
    } else if (thisMove.from.inWeb) {
        // TODO: Test.
        data.liftOffEnvelope = LiftOffEnvelope.NO_JUMP;
    } else if (resetFrom || thisMove.touchedGround) {
        // TODO: Where exactly to put noFallAssumeGround ?
        data.liftOffEnvelope = LiftOffEnvelope.NORMAL;
    } else {
    // Keep medium.
    // TODO: Is above stairs ?
    }
    // Count how long one is moving inside of a medium.
    if (oldLiftOffEnvelope != data.liftOffEnvelope) {
        data.insideMediumCount = 0;
        data.combinedMediumHCount = 0;
        data.combinedMediumHValue = 0.0;
    } else if (!resetFrom || !resetTo) {
        data.insideMediumCount = 0;
    } else {
        data.insideMediumCount++;
    }
    // Apply reset conditions.
    if (resetTo) {
        // The player has moved onto ground.
        if (toOnGround) {
            // Reset bunny-hop-delay.
            if (data.bunnyhopDelay > 0 && yDistance > 0.0 && to.getY() > data.getSetBackY() + 0.12 && !from.isResetCond() && !to.isResetCond()) {
                data.bunnyhopDelay = 0;
                tags.add("resetbunny");
            }
        }
        // Reset data.
        data.setSetBack(to);
        data.sfJumpPhase = 0;
        data.clearAccounting();
        data.sfNoLowJump = false;
        if (data.sfLowJump && resetFrom) {
            // Prevent reset if coming from air (purpose of the flag).
            data.sfLowJump = false;
        }
        if (hFreedom <= 0.0 && thisMove.verVelUsed == null) {
            data.resetVelocityJumpPhase(tags);
        }
    } else if (resetFrom) {
        // The player moved from ground.
        data.setSetBack(from);
        // This event is already in air.
        data.sfJumpPhase = 1;
        data.clearAccounting();
        data.sfLowJump = false;
    // not resetting nolowjump (?)...
    // Don't reset velocity phase unless moving into resetcond.
    // if (hFreedom <= 0.0 && data.verVelUsed == null && (!data.noFallAssumeGround || fromOnGround)) {
    // data.resetVelocityJumpPhase(tags);
    // }
    } else {
        data.sfJumpPhase++;
        // TODO: Void-to-void: Rather handle unified somewhere else (!).
        if (to.getY() < 0.0 && cc.sfSetBackPolicyVoid) {
            data.setSetBack(to);
        }
    }
    if (inAir) {
        // Adjust in-air counters.
        if (yDistance == 0.0) {
            data.sfZeroVdistRepeat++;
        } else {
            data.sfZeroVdistRepeat = 0;
        }
    } else {
        data.sfZeroVdistRepeat = 0;
        data.ws.resetConditions(WRPT.G_RESET_NOTINAIR);
        data.sfVLInAir = false;
    }
    // Horizontal velocity invalidation.
    if (hDistance <= (cc.velocityStrictInvalidation ? thisMove.hAllowedDistanceBase : thisMove.hAllowedDistanceBase / 2.0)) {
        // TODO: Should there be other side conditions?
        // Invalidate used horizontal velocity.
        // debug(player, "*** INVALIDATE ON SPEED");
        data.clearActiveHorVel();
    }
    // TODO: Pull down tick / timing data (perhaps add an API object for millis + source + tick + sequence count (+ source of sequence count).
    if (debug) {
        // TODO: Only update, if velocity is queued at all.
        data.getVerticalVelocityTracker().updateBlockedState(tick, // Assume blocked with being in web/water, despite not entirely correct.
        thisMove.headObstructed || thisMove.from.resetCond, // (Similar here.)
        thisMove.touchedGround || thisMove.to.resetCond);
        // TODO: TEST: Check unused velocity here too. (Should have more efficient process, pre-conditions for checking.)
        UnusedVelocity.checkUnusedVelocity(player, type, data, cc);
    }
    // Adjust data.
    data.lastFrictionHorizontal = data.nextFrictionHorizontal;
    data.lastFrictionVertical = data.nextFrictionVertical;
    // Log tags added after violation handling.
    if (debug && tags.size() > tagsLength) {
        logPostViolationTags(player);
    }
    return null;
}
Also used : LiftOffEnvelope(fr.neatmonster.nocheatplus.checks.moving.model.LiftOffEnvelope) PlayerMoveData(fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveData) PlayerLocation(fr.neatmonster.nocheatplus.utilities.location.PlayerLocation) Location(org.bukkit.Location)

Aggregations

LiftOffEnvelope (fr.neatmonster.nocheatplus.checks.moving.model.LiftOffEnvelope)1 PlayerMoveData (fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveData)1 PlayerLocation (fr.neatmonster.nocheatplus.utilities.location.PlayerLocation)1 Location (org.bukkit.Location)1