[BLEEDING, INSTABLE] Switch horizontal velocity to a new method.

This method remembers (currently) each velocity added and only uses it
when it is needed not to generate a violation. Further (currently
simple) activation and invalidation logics are applied. With this method
latency will be much less of a problem, though stacking of queued
entries (especially for rocket-boots style flying) and more
merging of entries and more invalidation logics are required, thus
bleeding+instable.

On the long term this should make cheating much more difficult, possible
steps are:
1. Use method for vertical velocity too (only positive)
2. Distinguish positive and negative vertical velocity (opens a way to
control the speed downwards in any medium!).
3. Per-axis velocity (either absolute or pos/neg with more invalidation
logic on direction changes).
This commit is contained in:
asofold 2013-03-08 04:06:17 +01:00
parent 24c1599e94
commit 5dd09d4476
9 changed files with 280 additions and 46 deletions

View File

@ -83,7 +83,23 @@ public class CreativeFly extends Check {
final double limitH = cc.creativeFlyHorizontalSpeed / 100D * HORIZONTAL_SPEED * fSpeed;
// Finally, determine how far the player went beyond the set limits.
double resultH = Math.max(0.0D, hDistance - data.horizontalFreedom - limitH);
// double resultH = Math.max(0.0D, hDistance - data.horizontalFreedom - limitH);
double resultH = Math.max(0.0D, hDistance - limitH);
// Check velocity.
if (resultH > 0){
double hFreedom = data.getHorizontalFreedom();
if (hFreedom < resultH){
// Use queued velocity if possible.
hFreedom += data.useHorizontalVelocity(resultH - hFreedom);
}
if (hFreedom > 0.0){
resultH = Math.max(0.0, resultH - hFreedom);
}
}
else{
data.hVelActive.clear(); // TODO: test/check !
}
final boolean sprinting = player.isSprinting() && player.getFoodLevel() > 5;

View File

@ -119,6 +119,7 @@ public class MovingConfig extends ACheckConfig {
// Special tolerance values:
/** This is not strictly ticks, but packets, for now.*/
public final int velocityGraceTicks;
public final int velocityActivationCounter;
public final double noFallyOnGround;
public final double yOnGround;
public final double yStep;
@ -183,6 +184,7 @@ public class MovingConfig extends ACheckConfig {
sfHoverViolation = config.getDouble(ConfPaths.MOVING_SURVIVALFLY_HOVER_SFVIOLATION);
velocityGraceTicks = config.getInt(ConfPaths.MOVING_VELOCITY_GRACETICKS);
velocityActivationCounter = config.getInt(ConfPaths.MOVING_VELOCITY_ACTIVATIONCOUNTER);
yOnGround = config.getDouble(ConfPaths.MOVING_YONGROUND, 0.001, 2.0, 0.0626); // sqrt(1/256), see: NetServerHandler.
noFallyOnGround = config.getDouble(ConfPaths.MOVING_NOFALL_YONGROUND, 0.001, 2.0, yOnGround);
// ystep is set to 0.45 by default, for stairs / steps.

View File

@ -1,6 +1,9 @@
package fr.neatmonster.nocheatplus.checks.moving;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.bukkit.Location;
@ -90,10 +93,14 @@ public class MovingData extends ACheckData {
public int verticalVelocityCounter;
public double verticalFreedom;
public double verticalVelocity;
public int verticalVelocityUsed = 0;
public int horizontalVelocityCounter;
public double horizontalFreedom;
public int horizontalVelocityUsed = 0;
public int verticalVelocityUsed = 0;
/** Active velocity entries (horizontal distance). */
public final List<Velocity> hVelActive = new LinkedList<Velocity>();
/** Queued velocity entries (horizontal distance). */
public final List<Velocity> hVelQueued = new LinkedList<Velocity>();
// public int horizontalVelocityCounter;
// public double horizontalFreedom;
// public int horizontalVelocityUsed = 0;
// Coordinates.
/** Last from coordinates. */
@ -176,6 +183,7 @@ public class MovingData extends ACheckData {
fromX = toX = Double.MAX_VALUE;
clearAccounting();
clearNoFallData();
removeAllVelocity();
sfHorizontalBuffer = 0;
sfHBufExtra = 0;
toWasReset = fromWasReset = false; // TODO: true maybe
@ -211,6 +219,7 @@ public class MovingData extends ACheckData {
sfHoverTicks = -1;
sfDirty = false;
mediumLiftOff = defaultMediumLiftOff;
removeAllVelocity();
}
/**
@ -403,5 +412,98 @@ public class MovingData extends ACheckData {
toY = to.getY();
toZ = to.getZ();
}
/**
* Add horizontal velocity (distance). <br>
* Since velocity is seldom an access method should be better. Flying players are expensive anyway, so this should not matter too much.
* @param vel
*/
public void addHorizontalVelocity(final Velocity vel) {
// TODO: Might merge entries !
hVelQueued.add(vel);
}
/**
* Currently only applies to horizontal velocity.
*/
public void removeAllVelocity(){
hVelActive.clear();
hVelQueued.clear();
}
/**
* Remove all velocity entries that are invalid. Checks both active and queued.
* <br>(This does not catch invalidation by speed / direction changing.)
*/
public void removeInvalidVelocity() {
// TODO: Also merge entries here, or just on adding?
Iterator<Velocity> it;
// Active.
it = hVelActive.iterator();
while (it.hasNext()){
final Velocity vel = it.next();
// TODO: 0.001 can be stretched somewhere else, most likely...
if (vel.valCount <= 0 || vel.value <= 0.001) it.remove();
}
// Queued.
it = hVelQueued.iterator();
while (it.hasNext()){
final Velocity vel = it.next();
if (vel.actCount <= 0) it.remove();
}
}
/**
* Called for moving events, increase age of velocity.
*/
public void velocityTick(){
// Increase counts for active.
for (final Velocity vel : hVelActive){
vel.actCount --;
vel.sum += vel.value;
vel.value *= 0.9; // TODO: Actual friction.
}
// Increase counts for queued.
final Iterator<Velocity> it = hVelQueued.iterator();
while (it.hasNext()){
it.next().actCount --;
}
}
/**
* Get effective amount of all used velocity.
* @return
*/
public double getHorizontalFreedom() {
// TODO: model/calculate it as accurate as possible...
double f = 0;
for (final Velocity vel : hVelActive){
f += vel.value;
}
return f;
}
/**
* Use all queued velocity until at least amount is matched.
* 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.
* @return
*/
public double useHorizontalVelocity(final double amount) {
final Iterator<Velocity> it = hVelQueued.iterator();
double used = 0;
while (it.hasNext()){
final Velocity vel = it.next();
used += vel.value;
hVelActive.add(vel);
it.remove();
if (used >= amount){
break;
}
}
return used;
}
}

View File

@ -455,6 +455,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
if (player.isInsideVehicle()){
// Workaround for pigs !
data.sfHoverTicks = -1;
data.removeAllVelocity();
final Entity vehicle = player.getVehicle();
if (vehicle != null && (vehicle instanceof Pig)){
onVehicleMove(new VehicleMoveEvent((Vehicle) vehicle, event.getFrom(), event.getFrom()));
@ -518,22 +519,24 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// Just try to estimate velocities over time. Not very precise, but works good enough most of the time. Do
// general data modifications one for each event.
// TODO: Rework to queued velocity entries: activation + invalidation
// Horizontal velocity.
if (data.horizontalVelocityCounter > 0D){
data.horizontalVelocityUsed ++;
data.horizontalVelocityCounter--;
data.horizontalFreedom = Math.max(0.0, data.horizontalFreedom - 0.09);
}
else if (data.horizontalFreedom > 0.001D){
if (data.verticalVelocityUsed == 1 && data.verticalVelocity > 0.5){
data.horizontalVelocityUsed = 0;
data.horizontalFreedom = 0;
}
else{
data.horizontalVelocityUsed ++;
data.horizontalFreedom *= 0.90D;
}
}
data.removeInvalidVelocity();
data.velocityTick();
// // Horizontal velocity.
// if (data.horizontalVelocityCounter > 0D){
// data.horizontalVelocityUsed ++;
// data.horizontalVelocityCounter--;
// data.horizontalFreedom = Math.max(0.0, data.horizontalFreedom - 0.09);
// }
// else if (data.horizontalFreedom > 0.001D){
// if (data.verticalVelocityUsed == 1 && data.verticalVelocity > 0.5){
// data.horizontalVelocityUsed = 0;
// data.horizontalFreedom = 0;
// }
// else{
// data.horizontalVelocityUsed ++;
// data.horizontalFreedom *= 0.90D;
// }
// }
// Vertical velocity.
if (data.verticalVelocity <= 0.09D){
data.verticalVelocityUsed ++;
@ -951,6 +954,8 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
if (cc.debug) System.out.println(event.getPlayer().getName() + " new velocity: " + velocity);
// TODO: Check for vehicles ?
double newVal = velocity.getY();
if (newVal >= 0D) {
if (data.verticalFreedom <= 0.001 && data.verticalVelocityCounter >= 0){
@ -964,9 +969,12 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
newVal = Math.sqrt(velocity.getX() * velocity.getX() + velocity.getZ() * velocity.getZ());
if (newVal > 0D) {
data.horizontalFreedom += newVal;
data.horizontalVelocityCounter = Math.min(100, Math.max(data.horizontalVelocityCounter, cc.velocityGraceTicks ) + 1 + (int) Math.round(newVal * 10.0)); // 30;
data.horizontalVelocityUsed = 0;
final Velocity vel = new Velocity(newVal, cc.velocityActivationCounter, 1 + (int) Math.round(newVal * 10.0));
data.removeInvalidVelocity();
data.addHorizontalVelocity(vel);
// data.horizontalFreedom += newVal;
// data.horizontalVelocityCounter = Math.min(100, Math.max(data.horizontalVelocityCounter, cc.velocityGraceTicks ) + 1 + (int) Math.round(newVal * 10.0)); // 30;
// data.horizontalVelocityUsed = 0;
}
// Set dirty flag here.
@ -1121,6 +1129,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
final MovingData data = MovingData.getData(player);
// TODO: on existing set back: detect world changes and loss of world on join (+ set up some paradigm).
data.clearMorePacketsData();
data.removeAllVelocity();
final Location loc = player.getLocation();
// Correct set-back on world changes.
@ -1167,7 +1176,8 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
private void onLeave(final Player player) {
survivalFly.setReallySneaking(player, false);
noFall.onLeave(player);
final MovingData data = MovingData.getData(player);
data.removeAllVelocity();
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@ -1199,9 +1209,11 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
data.resetPositions(loc);
data.setSetBack(loc);
// Experiment: add some velocity (fake).
data.horizontalVelocityCounter = 1;
data.horizontalFreedom = 0.9;
data.horizontalVelocityUsed = 0;
// data.horizontalVelocityCounter = 1;
// data.horizontalFreedom = 0.9;
// data.horizontalVelocityUsed = 0;
data.removeAllVelocity();
data.addHorizontalVelocity(new Velocity(0.9, 1, 1));
data.verticalVelocityCounter = 1;
data.verticalFreedom = 1.2;
data.verticalVelocity = 0.15;

View File

@ -2,6 +2,7 @@ package fr.neatmonster.nocheatplus.checks.moving;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@ -123,7 +124,8 @@ public class SurvivalFly extends Check {
}
// Judge if horizontal speed is above limit.
double hDistanceAboveLimit = hDistance - hAllowedDistance - data.horizontalFreedom;
// double hDistanceAboveLimit = hDistance - hAllowedDistance - data.horizontalFreedom;
double hDistanceAboveLimit = hDistance - hAllowedDistance;
if (hDistanceAboveLimit > 0){
// Check extra buffer (!).
final double extraUsed;
@ -139,10 +141,33 @@ public class SurvivalFly extends Check {
else{
extraUsed = 0.0;
}
// After failure permission checks ( + speed modifier + sneaking + blocking + speeding).
// Check velocity.
double hFreedom; // Horizontal freedom if used (!).
if (hDistanceAboveLimit > 0){
hFreedom = data.getHorizontalFreedom();
if (hFreedom < hDistanceAboveLimit){
// Use queued velocity if possible.
hFreedom += data.useHorizontalVelocity(hDistanceAboveLimit - hFreedom);
}
if (hFreedom > 0.0){
hDistanceAboveLimit = Math.max(0.0, hDistanceAboveLimit - hFreedom);
}
}
else{
data.hVelActive.clear(); // TODO: test/check !
hFreedom = 0;
}
// TODO: Use velocity already here ?
// After failure permission checks ( + speed modifier + sneaking + blocking + speeding) and velocity (!).
if (hDistanceAboveLimit > 0){
hAllowedDistance = getAllowedhDist(player, from, to, sprinting, hDistance, data, cc, true);
hDistanceAboveLimit = hDistance - hAllowedDistance - data.horizontalFreedom - extraUsed;
// hDistanceAboveLimit = hDistance - hAllowedDistance - data.horizontalFreedom - extraUsed;
if (hFreedom > 0){
hDistanceAboveLimit = hDistance - hAllowedDistance - extraUsed - hFreedom;
}
else{
hDistanceAboveLimit = hDistance - hAllowedDistance - extraUsed;
}
if (hAllowedDistance > 0){ // TODO: Fix !
// (Horizontal buffer might still get used.)
tags.add("hspeed");
@ -150,6 +175,7 @@ public class SurvivalFly extends Check {
}
}
else{
data.hVelActive.clear(); // TODO: test/check !
data.sfHBufExtra = 0;
}
///////
@ -164,7 +190,8 @@ public class SurvivalFly extends Check {
}
// Prevent players from sprinting if they're moving backwards.
if (hDistanceAboveLimit <= 0D && sprinting && data.horizontalFreedom <= 0.001D) {
// if (hDistanceAboveLimit <= 0D && sprinting && data.horizontalFreedom <= 0.001D) {
if (hDistanceAboveLimit <= 0D && sprinting && data.hVelActive.isEmpty()) {
final float yaw = from.getYaw();
if (xDistance < 0D && zDistance > 0D && yaw > 180F && yaw < 270F || xDistance < 0D && zDistance < 0D
&& yaw > 270F && yaw < 360F || xDistance > 0D && zDistance < 0D && yaw > 0F && yaw < 90F
@ -467,11 +494,6 @@ public class SurvivalFly extends Check {
data.verticalVelocity = 0;
data.verticalVelocityUsed = 0;
}
if (data.horizontalVelocityUsed > cc.velocityGraceTicks && hDistance < sprintingSpeed){
data.horizontalFreedom = 0;
data.horizontalVelocityCounter = 0;
data.horizontalVelocityUsed = 0;
}
}
else if (resetFrom){
// The player moved from ground.
@ -479,6 +501,18 @@ public class SurvivalFly extends Check {
data.sfJumpPhase = 1; // TODO: ?
data.clearAccounting();
}
// Check removal of active horizontal velocity.
if (hDistance <= hAllowedDistance){ // TODO: Check conditions etc.
// Invalidate used horizontal velocity.
data.hVelActive.clear();
// if (data.horizontalVelocityUsed > cc.velocityGraceTicks){
// data.horizontalFreedom = 0;
// data.horizontalVelocityCounter = 0;
// data.horizontalVelocityUsed = 0;
// }
}
data.sfLastYDist = yDistance;
return null;
}
@ -500,26 +534,40 @@ public class SurvivalFly extends Check {
private void outputDebug(final Player player, final PlayerLocation to, final MovingData data, final MovingConfig cc,
final double hDistance, final double hAllowedDistance, final double yDistance, final double vAllowedDistance,
final boolean fromOnGround, final boolean resetFrom, final boolean toOnGround, final boolean resetTo) {
// TODO: also show resetcond (!)
// TODO: Show player name once (!)
final StringBuilder builder = new StringBuilder(500);
final String hBuf = (data.sfHorizontalBuffer < 1.0 ? ((" hbuf=" + StringUtil.fdec3.format(data.sfHorizontalBuffer))) : "");
final String hBufExtra = (data.sfHBufExtra > 0 ? (" hbufextra=" + data.sfHBufExtra) : "");
builder.append(player.getName() + " ground: " + (data.noFallAssumeGround ? "(assumeonground) " : "") + (fromOnGround ? "onground -> " : (resetFrom ? "resetcond -> " : "--- -> ")) + (toOnGround ? "onground" : (resetTo ? "resetcond" : "---")) + ", jumpphase: " + data.sfJumpPhase);
builder.append("\n" + player.getName() + " hDist: " + StringUtil.fdec3.format(hDistance) + " / " + StringUtil.fdec3.format(hAllowedDistance) + hBuf + hBufExtra + " , vDist: " + StringUtil.fdec3.format(yDistance) + " (" + StringUtil.fdec3.format(to.getY() - data.getSetBackY()) + " / " + StringUtil.fdec3.format(vAllowedDistance) + ")");
builder.append(player.getName() + " SurvivalFly\nground: " + (data.noFallAssumeGround ? "(assumeonground) " : "") + (fromOnGround ? "onground -> " : (resetFrom ? "resetcond -> " : "--- -> ")) + (toOnGround ? "onground" : (resetTo ? "resetcond" : "---")) + ", jumpphase: " + data.sfJumpPhase);
builder.append("\n" + " hDist: " + StringUtil.fdec3.format(hDistance) + " / " + StringUtil.fdec3.format(hAllowedDistance) + hBuf + hBufExtra + " , vDist: " + StringUtil.fdec3.format(yDistance) + " (" + StringUtil.fdec3.format(to.getY() - data.getSetBackY()) + " / " + StringUtil.fdec3.format(vAllowedDistance) + ")");
if (data.verticalVelocityCounter > 0 || data.verticalFreedom >= 0.001){
builder.append("\n" + player.getName() + " vertical freedom: " + StringUtil.fdec3.format(data.verticalFreedom) + " (vel=" + StringUtil.fdec3.format(data.verticalVelocity) + "/counter=" + data.verticalVelocityCounter +"/used="+data.verticalVelocityUsed);
builder.append("\n" + " vertical freedom: " + StringUtil.fdec3.format(data.verticalFreedom) + " (vel=" + StringUtil.fdec3.format(data.verticalVelocity) + "/counter=" + data.verticalVelocityCounter +"/used="+data.verticalVelocityUsed);
}
if (data.horizontalVelocityCounter > 0 || data.horizontalFreedom >= 0.001){
builder.append("\n" + player.getName() + " horizontal freedom: " + StringUtil.fdec3.format(data.horizontalFreedom) + " (counter=" + data.horizontalVelocityCounter +"/used="+data.horizontalVelocityUsed);
// if (data.horizontalVelocityCounter > 0 || data.horizontalFreedom >= 0.001){
// builder.append("\n" + player.getName() + " horizontal freedom: " + StringUtil.fdec3.format(data.horizontalFreedom) + " (counter=" + data.horizontalVelocityCounter +"/used="+data.horizontalVelocityUsed);
// }
if (!data.hVelActive.isEmpty()){
builder.append("\n" + " horizontal velocity (active):");
addVeloctiy(builder, data.hVelActive);
}
if (!data.hVelQueued.isEmpty()){
builder.append("\n" + " horizontal velocity (queued):");
addVeloctiy(builder, data.hVelQueued);
}
if (!resetFrom && !resetTo) {
if (cc.survivalFlyAccountingV && data.vDistAcc.count() > data.vDistAcc.bucketCapacity()) builder.append("\n" + player.getName() + " vacc=" + data.vDistAcc.toInformalString());
if (cc.survivalFlyAccountingV && data.vDistAcc.count() > data.vDistAcc.bucketCapacity()) builder.append("\n" + " vacc=" + data.vDistAcc.toInformalString());
}
if (player.isSleeping()) tags.add("sleeping");
if (!tags.isEmpty()) builder.append("\n" + player.getName() + " tags: " + StringUtil.join(tags, "+"));
if (!tags.isEmpty()) builder.append("\n" + " tags: " + StringUtil.join(tags, "+"));
System.out.print(builder.toString());
}
private void addVeloctiy(final StringBuilder builder, final List<Velocity> entries) {
for (final Velocity vel: entries){
builder.append(" value=" + vel.value + " counter=" + vel.actCount);
}
}
/**
* Check if the player might have been on ground due to moving down and jumping up again, but somehow no event showing him on ground has been fired.
* @param player

View File

@ -0,0 +1,52 @@
package fr.neatmonster.nocheatplus.checks.moving;
/**
* One entry of velocity for a player. Might be used per axis or for horizontal/vertical.
* @author mc_dev
*
*/
public class Velocity {
/** The amount of velocity, decreasing with use. */
public double value;
/** "Some sum" for general purpose.
* <li> For vertical entries this is used to alter the allowed y-distance to the set-back point. </li>
*/
public double sum;
///////////////////////////
// Activation conditions.
///////////////////////////
// TODO: Add more conditions (ticks, ?real time)
/**
* Maximum count before activation.
*/
public int actCount;
///////////////////////////
// Invalidation conditions.
///////////////////////////
// TODO: Add more conditions (ticks, ?real time)
/**
* Count .
*/
public int valCount;
public Velocity(double value, int actCount, int valCount){
this.value = value;
this.actCount = actCount;
this.valCount = valCount;
}
}

View File

@ -3,7 +3,7 @@ package fr.neatmonster.nocheatplus.components;
/**
* ComponentRegistry:
* <li>Supported components: Listener, TickListener, PermStateReceiver, INotifyReload, INeedConfig, IRemoveData, MCAccessHolder, ConsistencyChecker</li>
* <li>Supported components: Listener, TickListener, PermStateReceiver, INotifyReload, INeedConfig, IRemoveData, MCAccessHolder, ConsistencyChecker, JoinLeaveListener</li>
* <li>Registering components should be done during onEnable or any time while the plugin is enabled, all components will be unregistered in onDisable.</li>
* <li>References to all components will be held until onDisable is finished.</li>
* <li>Interfaces checked for managed listeners: IHaveMethodOrder (method), ComponentWithName (tag)</li>

View File

@ -550,6 +550,7 @@ public abstract class ConfPaths {
// Special (to be sorted in or factored out).
private static final String MOVING_VELOCITY = MOVING + "velocity.";
public static final String MOVING_VELOCITY_GRACETICKS = MOVING_VELOCITY + "graceticks";
public static final String MOVING_VELOCITY_ACTIVATIONCOUNTER = MOVING_VELOCITY + "activationcounter";
public static final String MOVING_NOFALL_YONGROUND = MOVING_NOFALL + "yonground";
public static final String MOVING_YONGROUND = MOVING + "yonground";

View File

@ -430,6 +430,7 @@ public class DefaultConfig extends ConfigFile {
// Special.
set(ConfPaths.MOVING_VELOCITY_GRACETICKS, 20);
set(ConfPaths.MOVING_VELOCITY_ACTIVATIONCOUNTER, 80);
/*
* dP"8 d8 ,e,