[BLEEDING][BREAKING] Rework much of y-axis handling.

* In addition to the "distance from set-back" check, we have a check of
the per-move distance for in-air checks, taking account of friction.
* In-air and liquid checks should consume vertical velocity once needed.
* Model vertical velocity "exact", i.e. positive and negative, use an
entry once a sub-check fails, quite strict invalidation of not matching
values, matching against the y-distance directly.
* Vertical accounting has been sharpened for the moment. The new
per-move checking might make it superfluous.
* Remove MediumLiftOff in favor of a LiftOffEnvelope carrying basic
lift-off max-gain/max-height/max-phase, enabling to distinguish between
normal lift-off and liquid near ground.
* Rename others (e.g. sfLastYDist -> lastYDist). Thus breaking internal
naming, adding velocity via MovingData still works, but should behave
slightly differently.
* Fixes (waterwalk with head obstructed, resetting of sfDirty, possibly
others).

Issues.
* Edge cases with velocity, water.
* Lava needs friction, at least with velocity.
* Lostground_edge(ydist < 0.0) ->
bunny with yDistance > 0.0. Need more flags or better model for keeping
past moves information.
* Plain ground misses (layered snow).
* lostground with yDist == 0.0, then seemingly in-air yDist== 0.0, then
bunny/lifft-off (similar to above). Needs better modeling of past moves,
because several lostgorund cases mean "the move has been on ground".
Also includes geting the distance to ground for hack-proof set-back-y.
* Vertical velocity is now matched with a margin, because the client
seems to add randomly.
* Possibly new loopholes/exploits (extreme large moves?).
* Cleanup pending.
This commit is contained in:
asofold 2015-09-15 23:55:17 +02:00
parent f3a137709b
commit 8230a13fc0
15 changed files with 1006 additions and 475 deletions

View File

@ -13,7 +13,7 @@ import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.ViolationData;
import fr.neatmonster.nocheatplus.checks.moving.MovingConfig;
import fr.neatmonster.nocheatplus.checks.moving.MovingData;
import fr.neatmonster.nocheatplus.checks.moving.model.MediumLiftOff;
import fr.neatmonster.nocheatplus.checks.moving.model.LiftOffEnvelope;
import fr.neatmonster.nocheatplus.checks.moving.util.MovingUtil;
import fr.neatmonster.nocheatplus.permissions.Permissions;
import fr.neatmonster.nocheatplus.utilities.BlockProperties;
@ -60,8 +60,9 @@ public class Critical extends Check {
final MovingData dataM = MovingData.getData(player);
// TODO: Skip near the highest jump height (needs check if head collided with something solid, which also detects low jump).
if (!dataM.isVelocityJumpPhase() && (dataM.sfLowJump && !dataM.sfNoLowJump && dataM.mediumLiftOff == MediumLiftOff.GROUND
|| mcFallDistance < cc.criticalFallDistance && !BlockProperties.isResetCond(player, loc, mCc.yOnGround))) {
if (!dataM.isVelocityJumpPhase() &&
(dataM.sfLowJump && !dataM.sfNoLowJump && dataM.liftOffEnvelope == LiftOffEnvelope.NORMAL
|| mcFallDistance < cc.criticalFallDistance && !BlockProperties.isResetCond(player, loc, mCc.yOnGround))) {
final MovingConfig ccM = MovingConfig.getConfig(player);
if (MovingUtil.shouldCheckSurvivalFly(player, dataM, ccM)) {
data.criticalVL += 1.0;

View File

@ -27,12 +27,12 @@ import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.combined.Combined;
import fr.neatmonster.nocheatplus.checks.combined.Improbable;
import fr.neatmonster.nocheatplus.checks.inventory.Items;
import fr.neatmonster.nocheatplus.checks.moving.locations.LocationTrace;
import fr.neatmonster.nocheatplus.checks.moving.locations.LocationTrace.TraceEntry;
import fr.neatmonster.nocheatplus.checks.moving.model.MediumLiftOff;
import fr.neatmonster.nocheatplus.checks.moving.util.MovingUtil;
import fr.neatmonster.nocheatplus.checks.moving.MovingConfig;
import fr.neatmonster.nocheatplus.checks.moving.MovingData;
import fr.neatmonster.nocheatplus.checks.moving.locations.LocationTrace;
import fr.neatmonster.nocheatplus.checks.moving.locations.LocationTrace.TraceEntry;
import fr.neatmonster.nocheatplus.checks.moving.model.LiftOffEnvelope;
import fr.neatmonster.nocheatplus.checks.moving.util.MovingUtil;
import fr.neatmonster.nocheatplus.compat.BridgeHealth;
import fr.neatmonster.nocheatplus.components.JoinLeaveListener;
import fr.neatmonster.nocheatplus.logging.Streams;
@ -282,7 +282,7 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
if (!cancelled && TrigUtil.distance(loc.getX(), loc.getZ(), damagedLoc.getX(), damagedLoc.getZ()) < 4.5){
final MovingData mData = MovingData.getData(player);
// Check if fly checks is an issue at all, re-check "real sprinting".
if (mData.fromX != Double.MAX_VALUE && mData.mediumLiftOff != MediumLiftOff.LIMIT_JUMP){
if (mData.fromX != Double.MAX_VALUE && mData.liftOffEnvelope == LiftOffEnvelope.NORMAL) {
final double hDist = TrigUtil.distance(loc.getX(), loc.getZ(), mData.fromX, mData.fromZ);
if (hDist >= 0.23) {
// TODO: Might need to check hDist relative to speed / modifiers.

View File

@ -131,25 +131,29 @@ public class CreativeFly extends Check {
resultH *= 100D;
final double limitV = model.vMod / 100D * ModelFlying.VERTICAL_SPEED; // * data.jumpAmplifier;
// Super simple, just check distance compared to max distance vertical.
// TODO: max descending speed ! [max fall speed, use maximum with speed or added ?]
double limitV = model.vMod / 100D * ModelFlying.VERTICAL_SPEED; // * data.jumpAmplifier;
if (data.lastYDist != Double.MAX_VALUE) {
// (Disregard gravity.)
double frictionDist = data.lastYDist * SurvivalFly.FRICTION_MEDIUM_AIR;
if (!flying) {
frictionDist -= SurvivalFly.GRAVITY_MIN;
}
limitV = Math.max(frictionDist, limitV);
}
final double resultV;
if (yDistance > limitV && data.getOrUseVerticalVelocity(yDistance) != null) {
resultV = 0.0;
} else {
resultV = (yDistance - limitV) * 100D;
}
// TODO:_ signum considerations (aligned ...).
// double vDistanceAboveLimit = yDistance - data.getVerticalFreedom() - limitV;
// if (vDistanceAboveLimit > 0.0) {
// // TODO: consume / use vvel
// }
// final double resultV = (vDistanceAboveLimit - limitV) * 100D;
// final double result = Math.max(0.0, resultH) + Math.max(0D, resultV);
// Old handling.
final double verticalFreedom = data.getVerticalFreedom();
final double resultV = (yDistance - verticalFreedom - limitV) * 100D;
final double result = Math.max(0.0, resultH) + Math.max(0D, resultV);
if (cc.debug) {
outpuDebugMove(player, hDistance, limitH, yDistance, verticalFreedom, limitV);
outpuDebugMove(player, hDistance, limitH, yDistance, limitV, data);
}
// The player went to far, either horizontal or vertical.
@ -182,15 +186,21 @@ public class CreativeFly extends Check {
// Slowly reduce the violation level with each event.
data.creativeFlyVL *= 0.97D;
// If the event did not get cancelled, define a new setback point.
// Adjust the set-back and other last distances.
data.setSetBack(to);
data.lastHDist = hDistance;
data.lastYDist = yDistance;
return null;
}
private void outpuDebugMove(final Player player, final double hDistance, final double limitH, final double yDistance, final double verticalFreedom, final double limitV) {
private void outpuDebugMove(final Player player, final double hDistance, final double limitH, final double yDistance, final double limitV, final MovingData data) {
StringBuilder builder = new StringBuilder(350);
builder.append(player.getName());
builder.append(" CreativeFly hdist=" + hDistance + " hlimit=" + limitH + " ydist=" + yDistance + " vfreedom=" + verticalFreedom + " vlimit=" + limitV);
builder.append(" CreativeFly hdist=" + hDistance + " hlimit=" + limitH + " ydist=" + yDistance + " vlimit=" + limitV);
if (data.verVelUsed != null) {
builder.append(" vvel_use=" + data.verVelUsed);
}
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(Streams.TRACE_FILE, builder.toString());
}
}

View File

@ -13,15 +13,16 @@ import fr.neatmonster.nocheatplus.checks.access.CheckDataFactory;
import fr.neatmonster.nocheatplus.checks.access.ICheckData;
import fr.neatmonster.nocheatplus.checks.moving.locations.LocUtil;
import fr.neatmonster.nocheatplus.checks.moving.locations.LocationTrace;
import fr.neatmonster.nocheatplus.checks.moving.model.MediumLiftOff;
import fr.neatmonster.nocheatplus.checks.moving.model.LiftOffEnvelope;
import fr.neatmonster.nocheatplus.checks.moving.model.MoveConsistency;
import fr.neatmonster.nocheatplus.checks.moving.velocity.FrictionAxisVelocity;
import fr.neatmonster.nocheatplus.checks.moving.velocity.AccountEntry;
import fr.neatmonster.nocheatplus.checks.moving.velocity.FrictionAxisVelocity;
import fr.neatmonster.nocheatplus.checks.moving.velocity.SimpleAxisVelocity;
import fr.neatmonster.nocheatplus.checks.moving.velocity.SimpleEntry;
import fr.neatmonster.nocheatplus.logging.Streams;
import fr.neatmonster.nocheatplus.utilities.ActionAccumulator;
import fr.neatmonster.nocheatplus.utilities.ActionFrequency;
import fr.neatmonster.nocheatplus.utilities.PlayerLocation;
import fr.neatmonster.nocheatplus.utilities.StringUtil;
import fr.neatmonster.nocheatplus.utilities.TickTask;
/**
@ -29,13 +30,6 @@ import fr.neatmonster.nocheatplus.utilities.TickTask;
*/
public class MovingData extends ACheckData {
/**
* Assume the player has to move on ground or so to lift off. TODO: Test, might be better ground.
*/
private static final MediumLiftOff defaultMediumLiftOff = MediumLiftOff.LIMIT_JUMP;
// private static final long IGNORE_SETBACK_Y = BlockProperties.F_SOLID | BlockProperties.F_GROUND | BlockProperties.F_CLIMBABLE | BlockProperties.F_LIQUID;
/** The factory creating data. */
public static final CheckDataFactory factory = new CheckDataFactory() {
@Override
@ -59,7 +53,7 @@ public class MovingData extends ACheckData {
/**
* Gets the data of a specified player.
*
* final CheckDataFactory factory = new CheckDataFactory(
* @param player
* the player
* @return the data
@ -99,6 +93,19 @@ public class MovingData extends ACheckData {
}
}
// Check specific.
/**
* Default lift-off envelope, used after resetting. <br>
* TODO: Test, might be better ground.
*/
private static final LiftOffEnvelope defaultLiftOffEnvelope = LiftOffEnvelope.NO_JUMP;
/** Tolerance value for using vertical velocity (the client sends different values than received with fight damage). */
private static final double TOL_VVEL = 0.0625;
// private static final long IGNORE_SETBACK_Y = BlockProperties.F_SOLID | BlockProperties.F_GROUND | BlockProperties.F_CLIMBABLE | BlockProperties.F_LIQUID;
/////////////////
// Not static.
/////////////////
@ -112,10 +119,20 @@ public class MovingData extends ACheckData {
// Data shared between the fly checks -----
public int bunnyhopDelay;
public double jumpAmplifier;
public double jumpAmplifier = 0;
/** Last time the player was actually sprinting. */
public long timeSprinting = 0;
public double multSprinting = 1.30000002; // Multiplier at the last time sprinting.
/**
* Last valid y distance covered by a move. Integer.MAX_VALUE indicates "not set".
*/
public double lastYDist = Double.MAX_VALUE;
/**
* Last valid horizontal distance covered by a move. Integer.MAX_VALUE indicates "not set".
*/
public double lastHDist = Double.MAX_VALUE;
/** Only used during processing, to keep track of sub-checks using velocity. Reset in velocityTick, before checks run. */
public SimpleEntry verVelUsed = null;
/** Tick at which walk/fly speeds got changed last time. */
public int speedTick = 0;
@ -123,13 +140,8 @@ public class MovingData extends ACheckData {
public float flySpeed = 0.0f;
// Velocity handling.
// TODO: consider resetting these with clearFlyData and onSetBack.
/** Vertical velocity modeled as an axis (positive and negative possible) */
//private final FrictionAxisVelocity verVel = new FrictionAxisVelocity();
private int verticalVelocityCounter;
private double verticalFreedom;
private double verticalVelocity;
private int verticalVelocityUsed = 0;
private final SimpleAxisVelocity verVel = new SimpleAxisVelocity();
/** Horizontal velocity modeled as an axis (always positive) */
private final FrictionAxisVelocity horVel = new FrictionAxisVelocity();
@ -147,7 +159,8 @@ public class MovingData extends ACheckData {
// sf rather
/** To/from was ground or web or assumed to be etc. */
public boolean toWasReset, fromWasReset;
public MediumLiftOff mediumLiftOff = defaultMediumLiftOff;
/** Basic envelope constraints for switching into air. */
public LiftOffEnvelope liftOffEnvelope = defaultLiftOffEnvelope;
// Locations shared between all checks.
private Location setBack = null;
@ -185,22 +198,17 @@ public class MovingData extends ACheckData {
public double passableVL;
// Data of the survival fly check.
public double sfHorizontalBuffer = 0.0; // ineffective: SurvivalFly.hBufMax / 2.0;
public double sfHorizontalBuffer = 0.0; // ineffective: SurvivalFly.hBufMax / 2.0;
/** Event-counter to cover up for sprinting resetting server side only. Set in the FighListener. */
public int lostSprintCount = 0;
public int sfJumpPhase = 0;
public int lostSprintCount = 0;
public int sfJumpPhase = 0;
/** "Dirty" flag, for receiving velocity and similar while in air. */
private boolean sfDirty = false;
private boolean sfDirty = false;
/** Indicate low jumping descending phase (likely cheating). */
public boolean sfLowJump = false;
public boolean sfNoLowJump = false; // Hacks.
/**
* Last valid y distance covered by a move. Integer.MAX_VALUE indicates "not set".
*/
public double sfLastYDist = Double.MAX_VALUE;
public double sfLastHDist = Double.MAX_VALUE;
/** Counting while the player is not on ground and not moving. A value <0 means not hovering at all. */
public int sfHoverTicks = -1;
/** First count these down before incrementing sfHoverTicks. Set on join, if configured so. */
@ -243,7 +251,7 @@ public class MovingData extends ACheckData {
sfJumpPhase = 0;
jumpAmplifier = 0;
setBack = null;
sfLastYDist = sfLastHDist = Double.MAX_VALUE;
lastYDist = lastHDist = Double.MAX_VALUE;
fromX = toX = Double.MAX_VALUE;
toYaw = Float.MAX_VALUE;
clearAccounting();
@ -255,9 +263,10 @@ public class MovingData extends ACheckData {
sfHoverTicks = sfHoverLoginTicks = -1;
sfDirty = false;
sfLowJump = false;
mediumLiftOff = defaultMediumLiftOff;
liftOffEnvelope = defaultLiftOffEnvelope;
vehicleConsistency = MoveConsistency.INCONSISTENT;
lastFrictionHorizontal = lastFrictionVertical = 0.0;
verVelUsed = null;
}
/**
@ -287,7 +296,7 @@ public class MovingData extends ACheckData {
sfHoverTicks = -1; // 0 ?
sfDirty = false;
sfLowJump = false;
mediumLiftOff = defaultMediumLiftOff;
liftOffEnvelope = defaultLiftOffEnvelope;
removeAllVelocity();
vehicleConsistency = MoveConsistency.INCONSISTENT; // Not entirely sure here.
lastFrictionHorizontal = lastFrictionVertical = 0.0;
@ -299,7 +308,7 @@ public class MovingData extends ACheckData {
public void prepareSetBack(final Location loc) {
clearAccounting();
sfJumpPhase = 0;
sfLastYDist = sfLastHDist = Double.MAX_VALUE;
lastYDist = lastHDist = Double.MAX_VALUE;
toWasReset = false;
fromWasReset = false;
// Remember where we send the player to.
@ -352,15 +361,19 @@ public class MovingData extends ACheckData {
fromZ = toZ = z;
toYaw = yaw;
toPitch = pitch;
sfLastYDist = sfLastHDist = Double.MAX_VALUE;
lastYDist = lastHDist = Double.MAX_VALUE;
sfDirty = false;
sfLowJump = false;
mediumLiftOff = defaultMediumLiftOff;
liftOffEnvelope = defaultLiftOffEnvelope;
lastFrictionHorizontal = lastFrictionVertical = 0.0;
// TODO: other buffers ?
// No reset of vehicleConsistency.
}
public void resetLastDistances() {
lastHDist = lastYDist = 0.0;
}
/**
* Set positions according to a move (just to and from).
* @param from
@ -570,8 +583,47 @@ public class MovingData extends ACheckData {
}
/**
* Add horizontal velocity (distance). <br>
* @param vel Assumes positive values always.
* Add velocity to internal book-keeping.
* @param player
* @param data
* @param cc
* @param vx
* @param vy
* @param vz
*/
public void addVelocity(final Player player, final MovingConfig cc, final double vx, final double vy, final double vz) {
final int tick = TickTask.getTick();
removeInvalidVelocity(tick - cc.velocityActivationTicks);
if (debug) {
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(Streams.TRACE_FILE, player.getName() + " new velocity: " + vx + ", " + vy + ", " + vz);
}
// Always add vertical velocity.
verVel.add(new SimpleEntry(tick, vy, cc.velocityActivationCounter));
// TODO: Should also switch to adding always.
if (vx != 0.0 || vz != 0.0) {
final double newVal = Math.sqrt(vx * vx + vz * vz);
horVel.add(new AccountEntry(tick, newVal, cc.velocityActivationCounter, Math.max(20, 1 + (int) Math.round(newVal * 10.0))));
}
// Set dirty flag here.
sfDirty = true; // TODO: Set on using the velocity, due to latency !
sfNoLowJump = true; // TODO: Set on using the velocity, due top latency !
}
public void addVerticalVelocity(final SimpleEntry entry) {
verVel.add(entry);
}
/**
* Add horizontal velocity directly to horizontal-only bookkeeping.
*
* @param vel
* Assumes positive values always.
*/
public void addHorizontalVelocity(final AccountEntry vel) {
horVel.add(vel);
@ -582,7 +634,7 @@ public class MovingData extends ACheckData {
*/
public void removeAllVelocity() {
horVel.clear();
clearActiveVerVel(); // Until we have a better method.
verVel.clear();
sfDirty = false;
}
@ -593,7 +645,7 @@ public class MovingData extends ACheckData {
*/
public void removeInvalidVelocity(final int tick) {
horVel.removeInvalid(tick);
// verVel.removeInvalid(tick);
verVel.removeInvalid(tick);
}
/**
@ -624,17 +676,7 @@ public class MovingData extends ACheckData {
* @return
*/
public boolean hasAnyVerVel() {
return verticalFreedom >= 0.001 || verticalVelocityCounter > 0;
}
/**
* Clear active vertical velocity (until recoded, this will remove all vertical velocity).
*/
public void clearActiveVerVel() {
verticalFreedom = 0.0;
verticalVelocity = 0.0;
verticalVelocityCounter = 0;
verticalVelocityUsed = 0;
return verVel.hasQueued();
}
// public boolean hasActiveVerVel() {
@ -646,43 +688,24 @@ public class MovingData extends ACheckData {
// }
/**
* Called for moving events, increase age of velocity, decrease amounts, check which entries are invalid. Both horizontal and vertical.
* Called for moving events. Remove invalid entries, increase age of velocity, decrease amounts, check which entries are invalid. Both horizontal and vertical.
*/
public void velocityTick() {
public void velocityTick(final int invalidateBeforeTick) {
// Remove invalid velocity.
removeInvalidVelocity(invalidateBeforeTick);
// Horizontal velocity (intermediate concept).
horVel.tick();
// Vertical velocity (new concept).
// verVel.tick();
if (verticalVelocity <= 0.09) {
verticalVelocityUsed ++;
verticalVelocityCounter--;
}
// (Vertical velocity does not tick.)
if (verticalVelocityCounter > 0) {
if (verticalVelocity > 0.09) {
verticalVelocityUsed ++;
}
verticalFreedom += verticalVelocity;
verticalVelocity = Math.max(0.0, verticalVelocity -0.09);
// TODO: Consider using up counter ? / better use velocity entries / even better use x,y,z entries right away .
} else if (verticalFreedom > 0.001) {
if (verticalVelocityUsed == 1 && verticalVelocity > 1.0) {
// Workarounds.
verticalVelocityUsed = 0;
verticalVelocity = 0;
verticalFreedom = 0;
}
else{
// Counter has run out, now reduce the vertical freedom over time.
verticalVelocityUsed ++;
verticalFreedom *= 0.5;
}
}
if (!sfDirty && (horVel.hasActive() || horVel.hasQueued() || verticalFreedom > 0.001)) {
// Renew the dirty phase.
// Renew the dirty phase.
if (!sfDirty && (horVel.hasActive() || horVel.hasQueued())) {
sfDirty = true;
}
// Reset the "just used" velocity.
verVelUsed = null;
}
/**
@ -698,11 +721,15 @@ public class MovingData extends ACheckData {
* Amount is the horizontal distance that is to be covered by velocity (active has already been checked).
* <br>
* If the modeling changes (max instead of sum or similar), then this will be affected.
* @param amount The amount used.
* @param amount The amount demanded, must be positive.
* @return
*/
public double useHorizontalVelocity(final double amount) {
return horVel.use(amount);
final double available = horVel.use(amount);
if (available >= amount) {
sfDirty = true;
}
return available;
}
/**
@ -716,7 +743,50 @@ public class MovingData extends ACheckData {
}
if (horVel.hasQueued()) {
builder.append("\n" + " horizontal velocity (queued):");
horVel.AddQueued(builder);
horVel.addQueued(builder);
}
}
/**
* Get the first matching velocity entry (invalidate others). Sets
* verVelUsed if available.
*
* @param amount
* @return
*/
public SimpleEntry useVerticalVelocity(final double amount) {
final SimpleEntry available = verVel.use(amount, TOL_VVEL);
if (available != null) {
sfDirty = true;
verVelUsed = available;
}
return available;
}
/**
* Check the verVelUsed field and return that if appropriate. Otherwise
* call useVerticalVelocity(amount).
*
* @param amount
* @return
*/
public SimpleEntry getOrUseVerticalVelocity(final double amount) {
if (verVelUsed != null) {
if (verVel.matchesEntry(verVelUsed, amount, TOL_VVEL)) {
return verVelUsed;
}
}
return useVerticalVelocity(amount);
}
/**
* Debugging.
* @param builder
*/
public void addVerticalVelocity(final StringBuilder builder) {
if (verVel.hasQueued()) {
builder.append("\n" + " vertical velocity (queued):");
verVel.addQueued(builder);
}
}
@ -789,6 +859,27 @@ public class MovingData extends ACheckData {
}
}
/**
* Adjust on set back and similar.
* @param loc
*/
public void adjustLiftOffEnvelope(final PlayerLocation loc) {
// Simplified.
if (loc.isInWeb()) {
liftOffEnvelope = LiftOffEnvelope.NO_JUMP;
}
else if (loc.isInLiquid()) {
// TODO: Distinguish strong limit.
liftOffEnvelope = LiftOffEnvelope.LIMIT_LIQUID;
}
else if (loc.isOnGround()) {
liftOffEnvelope = LiftOffEnvelope.NORMAL;
}
else {
liftOffEnvelope = defaultLiftOffEnvelope;
}
}
/**
* This tests for a LocationTrace instance being set at all, not for locations having been added.
* @return
@ -852,53 +943,6 @@ public class MovingData extends ACheckData {
trace = null;
}
/**
* Add velocity to internal book-keeping.
* @param player
* @param data
* @param cc
* @param vx
* @param vy
* @param vz
*/
public void addVelocity(final Player player, final MovingConfig cc, final double vx, final double vy, final double vz) {
final int tick = TickTask.getTick();
removeInvalidVelocity(tick - cc.velocityActivationTicks);
if (debug) {
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(Streams.TRACE_FILE, player.getName() + " new velocity: " + vx + ", " + vy + ", " + vz);
}
boolean used = false;
if (vy > 0D) {
used = true;
if (verticalFreedom <= 0.001 && verticalVelocityCounter >= 0) {
verticalVelocity = 0;
}
verticalVelocity += vy;
verticalFreedom += verticalVelocity;
verticalVelocityCounter = Math.min(100, Math.max(verticalVelocityCounter, cc.velocityGraceTicks ) + 1 + (int) Math.round(vy * 10.0)); // 50;
verticalVelocityUsed = 0;
}
if (vx != 0.0 || vz != 0.0) {
final double newVal = Math.sqrt(vx * vx + vz * vz);
used = true;
final AccountEntry vel = new AccountEntry(tick, newVal, cc.velocityActivationCounter, Math.max(20, 1 + (int) Math.round(newVal * 10.0)));
addHorizontalVelocity(vel);
}
// Set dirty flag here.
if (used) {
// TODO: Detect when actually used? More complicated, some internal adding needs setting it here.
sfDirty = true;
sfNoLowJump = true;
}
// TODO: clear accounting here ?
}
/**
* Test if velocity has affected the in-air jumping phase. Keeps set until
* reset on-ground or otherwise. Use clearActiveVerVel to force end velocity
@ -911,27 +955,6 @@ public class MovingData extends ACheckData {
return sfDirty;
}
/**
* Refactoring state: Get the classic verticalFreedom.
* @return
*/
public double getVerticalFreedom() {
return verticalFreedom;
}
/**
* Refactoring stage: Fake/override vertical velocity.
* @param velocity
* @param freedom
* @param counter
*/
public void fakeVerticalFreedom(final double velocity, final double freedom, final int counter) {
verticalVelocity = velocity;
verticalFreedom = freedom;
verticalVelocityCounter = counter;
verticalVelocityUsed = 0;
}
/**
* Refactoring stage: Test which value sfDirty should have and set
* accordingly. This should only be called, if the player reached ground.
@ -939,7 +962,8 @@ public class MovingData extends ACheckData {
* @return If the velocity jump phase is still active (sfDirty).
*/
public boolean resetVelocityJumpPhase() {
if (verticalFreedom > 0.001 || horVel.hasActive() || horVel.hasQueued()) {
if (horVel.hasActive() || horVel.hasQueued()) {
// TODO: What with vertical ?
sfDirty = true;
} else {
sfDirty = false;
@ -948,37 +972,12 @@ public class MovingData extends ACheckData {
}
/**
* Add to builder with a leading newline, if counter or counter or freedom
* is above threshold (0 / 0.001).
*
* @param builder
* Force set the move to be affected by previous speed. Currently
* implemented as setting velocity jump phase.
*/
public void logVerticalFreedom(StringBuilder builder) {
if (verticalVelocityCounter > 0 || verticalFreedom >= 0.001) {
builder.append("\n vertical freedom: " + StringUtil.fdec3.format(verticalFreedom) + " (vel=" + StringUtil.fdec3.format(verticalVelocity) + "/counter=" + verticalVelocityCounter +"/used=" + verticalVelocityUsed);
}
}
/**
* Refactoring stage: Invalidate vertical velocity based on checking vs.
* configured grace ticks.
*
* @param velocityGraceTicks Ticks to check used ticks vs.
* @param removeFreedom If to reset verticalFreedom and use count as well.
* @return If actually reset.
*/
public boolean invalidateVerVelGrace(final int velocityGraceTicks, final boolean removeFreedom) {
if (verticalVelocityUsed > velocityGraceTicks) {
verticalVelocityCounter = 0;
verticalVelocity = 0;
if (removeFreedom) {
verticalVelocityUsed = 0;
verticalFreedom = 0;
}
return true;
} else {
return false;
}
public void setFrictionJumpPhase() {
// TODO: Better and more reliable modeling.
sfDirty = true;
}
}

View File

@ -54,10 +54,11 @@ import fr.neatmonster.nocheatplus.checks.combined.CombinedData;
import fr.neatmonster.nocheatplus.checks.moving.locations.LocUtil;
import fr.neatmonster.nocheatplus.checks.moving.locations.MoveInfo;
import fr.neatmonster.nocheatplus.checks.moving.locations.VehicleSetBack;
import fr.neatmonster.nocheatplus.checks.moving.model.MediumLiftOff;
import fr.neatmonster.nocheatplus.checks.moving.model.LiftOffEnvelope;
import fr.neatmonster.nocheatplus.checks.moving.model.MoveConsistency;
import fr.neatmonster.nocheatplus.checks.moving.util.MovingUtil;
import fr.neatmonster.nocheatplus.checks.moving.velocity.AccountEntry;
import fr.neatmonster.nocheatplus.checks.moving.velocity.SimpleEntry;
import fr.neatmonster.nocheatplus.compat.BridgeHealth;
import fr.neatmonster.nocheatplus.compat.BridgeMisc;
import fr.neatmonster.nocheatplus.components.IData;
@ -498,8 +499,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// Velocity tick (decrease + invalidation).
// TODO: Rework to generic (?) queued velocity entries: activation + invalidation
final int tick = TickTask.getTick();
data.removeInvalidVelocity(tick - cc.velocityActivationTicks);
data.velocityTick();
data.velocityTick(tick - cc.velocityActivationTicks);
// Check which fly check to use.
final boolean checkCf;
@ -606,7 +606,8 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
mightSkipNoFall = false;
}
}
if (!mightSkipNoFall) {
if (!mightSkipNoFall && (!pTo.isResetCond() || !pFrom.isResetCond())) {
// (Don't deal damage where no fall damage is possible.)
noFall.checkDamage(player, data, Math.min(Math.min(from.getY(), to.getY()), loc.getY()));
}
}
@ -724,6 +725,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// Reset some data.
data.prepareSetBack(newTo);
data.resetPositions(newTo); // TODO: Might move into prepareSetBack, experimental here.
adjustLiftOffEnvelope(player, newTo, data, cc);
// Set new to-location.
event.setTo(newTo);
@ -907,6 +909,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// If it was a teleport initialized by NoCheatPlus, do it anyway even if another plugin said "no".
Location to = event.getTo();
final Location ref;
final MovingConfig cc = MovingConfig.getConfig(player);
if (teleported != null && teleported.equals(to)) {
// Teleport by NCP.
// Prevent cheaters getting rid of flying data (morepackets, other).
@ -923,8 +926,8 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
}
// TODO: This could be done on MONITOR.
data.onSetBack(teleported);
adjustLiftOffEnvelope(player, teleported, data, cc);
} else {
final MovingConfig cc = MovingConfig.getConfig(player);
// Only if it wasn't NoCheatPlus, drop data from more packets check.
if (to != null && !event.isCancelled()) {
// Normal teleport.
@ -988,6 +991,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
if (data.hasSetBack() && !data.hasSetBackWorldChanged(to)) {
ref = data.getSetBack(to);
event.setTo(ref);
adjustLiftOffEnvelope(player, ref, data, cc);
}
else{
ref = from; // Player.getLocation ?
@ -1014,16 +1018,17 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// "real" teleport
ref = to;
double fallDistance = data.noFallFallDistance;
final MediumLiftOff oldMLO = data.mediumLiftOff; // Remember for workarounds.
final LiftOffEnvelope oldEnv = data.liftOffEnvelope; // Remember for workarounds.
data.clearMorePacketsData();
data.clearFlyData();
data.resetPositions(to);
if (TrigUtil.maxDistance(from.getX(), from.getY(), from.getZ(), to.getX(), to.getY(), to.getZ()) <= 12.0) {
// TODO: Might happen with bigger distances (mainly ender pearl thrown at others).
// Keep old MediumLiftOff.
data.mediumLiftOff = oldMLO;
// Keep old lift-off envelope.
data.liftOffEnvelope = oldEnv;
}
data.setSetBack(to);
adjustLiftOffEnvelope(player, to, data, cc);
// TODO: How to account for plugins that reset the fall distance here?
if (fallDistance > 1.0 && fallDistance - player.getFallDistance() > 0.0) {
// Reset fall distance if set so in the config.
@ -1057,10 +1062,26 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// Reset stuff.
Combined.resetYawRate(player, ref.getYaw(), System.currentTimeMillis(), true);
data.resetTeleported();
data.resetLastDistances();
// Prevent further moving processing for nested events.
processingEvents.remove(player.getName());
}
/**
* Simple adjustment after set-back and similar.
* @param player
* @param loc
* @param data
* @param cc
*/
private void adjustLiftOffEnvelope(final Player player, final Location loc, final MovingData data, final MovingConfig cc) {
final MoveInfo info = useMoveInfo();
info.set(player, loc, null, cc.yOnGround);
data.adjustLiftOffEnvelope(info.from);
info.cleanup();
returnMoveInfo(info);
}
/**
* Player got a velocity packet. The server can't keep track of actual velocity values (by design), so we have to
* try and do that ourselves. Very rough estimates.
@ -1320,8 +1341,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
data.resetPositions(loc); // TODO: See above.
}
// Always reset position to this one.
// TODO: more fine grained reset?
data.resetLastDistances();
data.clearMorePacketsData();
data.removeAllVelocity();
data.resetTrace(loc, tick, cc.traceSize, cc.traceMergeDist); // Might reset to loc instead of set-back ?
@ -1332,7 +1352,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
moveInfo.set(player, loc, null, cc.yOnGround);
data.toWasReset = moveInfo.from.isOnGroundOrResetCond();
if (data.toWasReset && moveInfo.from.isOnGround() && !moveInfo.from.isResetCond()) {
data.mediumLiftOff = MediumLiftOff.GROUND;
data.liftOffEnvelope = LiftOffEnvelope.NORMAL;
}
returnMoveInfo(moveInfo);
data.fromWasReset = data.toWasReset;
@ -1532,8 +1552,9 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
data.setSetBack(loc);
// Give some freedom to allow the "exiting move".
data.removeAllVelocity();
// TODO: Use-once entries usually are intended to allow one offset, but not jumping/flying on.
data.addHorizontalVelocity(new AccountEntry(0.9, 1, 1));
data.fakeVerticalFreedom(0.15, 1.2, 1);
data.addVerticalVelocity(new SimpleEntry(0.6, 1)); // TODO: Typical margin?
useLoc.setWorld(null);
}

View File

@ -0,0 +1,69 @@
package fr.neatmonster.nocheatplus.checks.moving.model;
/**
* Basic preset envelopes for moving off one medium.
*
* @author asofold
*
*/
public enum LiftOffEnvelope {
/** Normal in-air lift off without any restrictions/specialties. */
NORMAL(0.42, 1.35, 6, true),
/** Weak or no limit moving off liquid near ground. */
LIMIT_NEAR_GROUND(0.42, 1.35, 6, false), // TODO: 0.385 / not jump on top of 1 high wall from water.
/** Simple calm water surface. */
LIMIT_LIQUID(0.1, 0.27, 3, false), // TODO:
// /** Flowing water / strong(-est) limit. */
// LIMIT_LIQUID_STRONG(...), // TODO
/** No jumping at all. */
NO_JUMP(0.0, 0.0, 0, false); // TODO
;
private double maxJumpGain;
private double maxJumpHeight;
private int maxJumpPhase;
private boolean jumpEffectApplies;
private LiftOffEnvelope(double maxJumpGain, double maxJumpHeight, int maxJumpPhase, boolean jumpEffectApplies) {
this.maxJumpGain = maxJumpGain;
this.maxJumpHeight = maxJumpHeight;
this.maxJumpPhase = maxJumpPhase;
this.jumpEffectApplies = jumpEffectApplies;
}
public double getMaxJumpGain(double jumpAmplifier) {
// TODO: Count in effect level.
if (jumpEffectApplies && jumpAmplifier != 0.0) {
return Math.max(0.0, maxJumpGain + 0.2 * jumpAmplifier);
}
else {
return maxJumpGain;
}
}
public double getMaxJumpHeight(double jumpAmplifier) {
// TODO: Count in effect level.
if (jumpEffectApplies && jumpAmplifier > 0.0) {
return 1.35 + 0.6 + jumpAmplifier - 1.0;
} // TODO: < 0.0 ?
else {
return maxJumpHeight;
}
}
public int getMaxJumpPhase(double jumpAmplifier) {
if (jumpEffectApplies && jumpAmplifier > 0.0) {
return (int) Math.round((0.5 + jumpAmplifier) * (double) maxJumpPhase);
} // TODO: < 0.0 ?
else {
return maxJumpPhase;
}
}
public boolean jumpEffectApplies() {
return jumpEffectApplies;
}
}

View File

@ -1,17 +0,0 @@
package fr.neatmonster.nocheatplus.checks.moving.model;
/**
* This is for tracking what medium a player has been in before lift-off.
* @author mc_dev
*
*/
public enum MediumLiftOff {
/** Ordinary ground, normal jumping. */
GROUND,
/**
* Used for reduced jumping height. Until known better this is used for liquids, cobweb.
*/
LIMIT_JUMP,
// TODO: Might add NO_JUMP (web?).
}

View File

@ -180,16 +180,4 @@ public class MovingUtil {
}
}
/**
* Estimated lift-off distance for jumping, considering the stored jump
* effect.
*
* @param player
* @param data
* @return
*/
public static double estimateJumpLiftOff(final Player player, final MovingData data, final double add) {
return Math.max(0.0, 0.42 + add + 0.2 * data.jumpAmplifier);
}
}

View File

@ -175,7 +175,7 @@ public class FrictionAxisVelocity {
* Debugging.
* @param builder
*/
public void AddQueued(StringBuilder builder) {
public void addQueued(StringBuilder builder) {
addVeloctiy(builder, queued);
}

View File

@ -6,7 +6,7 @@ import java.util.List;
/**
* Simple per-axis velocity (positive + negative), only accounting for queuing
* and invalidation. Since entries just store values for one time use, no extra
* and invalidation. Since entries just wrap values for one time use, no extra
* ticking is done, invalidation mechanics and activation count decreasing takes
* place in removeInvalid.
*
@ -15,6 +15,9 @@ import java.util.List;
*/
public class SimpleAxisVelocity {
/** Margin for accepting a demanded 0.0 amount, regardless sign. */
private static final double marginAcceptZero = 0.005;
private final List<SimpleEntry> queued = new LinkedList<SimpleEntry>();
public void add(SimpleEntry entry) {
@ -26,51 +29,49 @@ public class SimpleAxisVelocity {
}
/**
* The value of the first matching entry. Returns 0.0 if no entry
* is available. This will directly invalidate leading entries with the
* wrong sign.
* Use the next matching entry.
*
* @param amount
* @return
* @param tolerance
* Allow using entries with less amount (still sign-specific).
* Must be equal or greater than 0.0.
* @return The first matching entry. Returns null if no entry is available.
* This will directly invalidate leading entries with the wrong
* sign.
*/
public double use(final double amount) {
public SimpleEntry use(final double amount, final double tolerance) {
final Iterator<SimpleEntry> it = queued.iterator();
final double absAmount = Math.abs(amount);
while (it.hasNext()) {
final SimpleEntry entry = it.next();
it.remove();
if (absAmount <= Math.abs(entry.value)) {
if (amount > 0.0 && entry.value > 0.0 || amount < 0.0 && entry.value < 0.0) {
// Success. Note that 0.0 Entries are ignored (should not exist anyway).
return entry.value;
} // else: Wrong sign.
} // else: Value too small.
if (matchesEntry(entry, amount, tolerance)) {
// Success.
return entry;
}
// (Entry can not be used.)
// TODO: Note unused velocity.
}
// None found.
return 0.0;
return null;
}
/**
* Check if the demanded amount can be covered by this velocity entry. Might
* return an entry with a small value with a different sign, if amount is
* set to 0.0. Needed also for testing stored entries.
*
* @return The value of the first matching positive entry. Returns 0.0 if no
* entry is available. This will directly invalidate leading entries
* with the wrong sign.
* @param entry
* @param amount
* @param tolerance
* Allow using entries with less amount (still sign-specific).
* Must be equal or greater than 0.0.
* @return
*/
public double usePositive() {
return use(Double.MIN_VALUE);
}
/**
* Use any negative amount.
*
* @return The value of the first matching negative entry. Returns 0.0 if no
* entry is available. This will directly invalidate leading entries
* with the wrong sign.
*/
public double useNegative() {
return use(-Double.MIN_VALUE);
public boolean matchesEntry(final SimpleEntry entry, final double amount, final double tolerance) {
return Math.abs(amount) <= Math.abs(entry.value) + tolerance &&
(amount > 0.0 && entry.value > 0.0 && amount <= entry.value + tolerance
|| amount < 0.0 && entry.value < 0.0 && entry.value - tolerance <= amount
|| amount == 0.0 && Math.abs(entry.value) <= marginAcceptZero);
}
/**
@ -78,6 +79,7 @@ public class SimpleAxisVelocity {
* @param tick
*/
public void removeInvalid(final int tick) {
// Note: clear invalidated here, append unused to invalidated.
final Iterator<SimpleEntry> it = queued.iterator();
while (it.hasNext()) {
final SimpleEntry entry = it.next();
@ -92,4 +94,15 @@ public class SimpleAxisVelocity {
queued.clear();
}
/**
* Debugging.
* @param builder
*/
public void addQueued(final StringBuilder builder) {
for (final SimpleEntry vel: queued) {
builder.append(" ");
builder.append(vel);
}
}
}

View File

@ -20,13 +20,13 @@ public class SimpleEntry {
// TODO: Add more conditions (max tick, real time ?)
public SimpleEntry(double value, int actCount, int valCount){
public SimpleEntry(double value, int actCount){
this.tick = TickTask.getTick();
this.value = value;
this.actCount = actCount;
}
public SimpleEntry(int tick, double value, int actCount, int valCount){
public SimpleEntry(int tick, double value, int actCount){
this.tick = tick;
this.value = value;
this.actCount = actCount;

View File

@ -1302,17 +1302,39 @@ public class BlockProperties {
}
/**
* @deprecated Checks for lava and water and only stationary. Long outdated. Subject to removal.
* @param blockId
* Simple checking method, heavy. No isIllegal check.
* @param player
* @param location
* @param yOnGround
* @return
*/
public static boolean isInWater(final int blockId) {
if (blockId == Material.STATIONARY_WATER.getId() || blockId == Material.STATIONARY_LAVA.getId()) {
return true;
}
// TODO: count in water height ?
// TODO: lava ?
return false;
public static boolean isInLiquid(final Player player, final Location location, final double yOnGround) {
// Bit fat workaround, maybe put the object through from check listener ?
blockCache.setAccess(location.getWorld());
pLoc.setBlockCache(blockCache);
pLoc.set(location, player, yOnGround);
final boolean res = pLoc.isInLiquid();
blockCache.cleanup();
pLoc.cleanup();
return res;
}
/**
* Simple checking method, heavy. No isIllegal check.
* @param player
* @param location
* @param yOnGround
* @return
*/
public static boolean isInWeb(final Player player, final Location location, final double yOnGround) {
// Bit fat workaround, maybe put the object through from check listener ?
blockCache.setAccess(location.getWorld());
pLoc.setBlockCache(blockCache);
pLoc.set(location, player, yOnGround);
final boolean res = pLoc.isInWeb();
blockCache.cleanup();
pLoc.cleanup();
return res;
}
/**
@ -1327,10 +1349,10 @@ public class BlockProperties {
blockCache.setAccess(location.getWorld());
pLoc.setBlockCache(blockCache);
pLoc.set(location, player, yOnGround);
final boolean onGround = pLoc.isOnGround();
final boolean res = pLoc.isOnGround();
blockCache.cleanup();
pLoc.cleanup();
return onGround;
return res;
}
/**

View File

@ -665,6 +665,17 @@ public class PlayerLocation {
return BlockProperties.collides(blockCache, x - width, y + eyeHeight, z - width, x + width, y + eyeHeight + marginAboveHeight, z + width, BlockProperties.F_GROUND | BlockProperties.F_SOLID);
}
/**
* Test if something solid/ground-like collides within a default margin
* above the height of the player. Margin is yOnGround + max(0.0, 2.0 - eyeHeight).
*
* @param marginAboveHeight
* @return
*/
public boolean isHeadObstructed() {
return isHeadObstructed(Math.max(0.0, 2.0 - eyeHeight) + yOnGround);
}
/**
* Convenience method for testing for either.
* @return
@ -953,4 +964,19 @@ public class PlayerLocation {
this.blockFlags = other.blockFlags; // Assume set.
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder(128);
builder.append("PlayerLocation(");
builder.append(world == null ? "null" : world.getName());
builder.append('/');
builder.append(Double.toString(x));
builder.append(", ");
builder.append(Double.toString(y));
builder.append(", ");
builder.append(Double.toString(z));
builder.append(')');
return builder.toString();
}
}

View File

@ -10,11 +10,11 @@ public class NoobsTest {
public void testSmallDoubles() {
double x;
x = Double.MIN_VALUE;
if (x <= 0.0) {
if (x <= 0.0 || !(x > 0.0)) {
fail("noob");
}
x = -Double.MIN_VALUE;
if (x >= 0.0) {
if (x >= 0.0 || !(x < 0.0)) {
fail("noob");
}
}