[BLEEDING] Maintain location traces (not making use of those, yet).

No change for the function of the plugin, except if there are new bugs.
Actual changes:
* Keep track of squared distance to the previous element in a trace.
* Balance mergeDist: prevent merging the latest entry, if the squared
	distance from the latest to the second latest element is greater
	than mergeDist. Never merge if there are only two entries in total.
* Add convenience methods for resetting and updating the trace.
* Maintain the moving traces actively. With intended use being
	player-player interaction, we will not reset with every teleport.
This commit is contained in:
asofold 2014-03-21 20:29:09 +01:00
parent 520e7bab74
commit c27c03cf8e
7 changed files with 191 additions and 29 deletions

View File

@ -1,10 +1,18 @@
package fr.neatmonster.nocheatplus.checks.moving;
import java.util.Iterator;
import fr.neatmonster.nocheatplus.utilities.TrigUtil;
/**
* This class is meant to record locations for players moving, in order to allow to be more
* lenient for the case of latency.
* lenient for the case of latency for player-player interaction such as with fighting.
* <br>
* NOTES on intended use:<br>
* <li>Is meant to always carry some location.</li>
* <li>Records only the end-positions of a move.</li>
* <li>Prefer calling add(...) with the current location, before iterating. Alternative: guard with isEmpty().</li>
* <li>Updating on teleport events is not intended - if the distance is too big, Minecraft should prevent interaction anyway.</li>
* @author mc_dev
*
*/
@ -16,12 +24,14 @@ public class LocationTrace {
public long time;
/** Coordinates. */
public double x, y, z;
public double lastDistSq;
public void set(long time, double x, double y, double z) {
public void set(long time, double x, double y, double z, double lastDistSq) {
this.x = x;
this.y = y;
this.z = z;
this.time = time;
this.lastDistSq = lastDistSq;
}
}
@ -30,7 +40,7 @@ public class LocationTrace {
* @author mc_dev
*
*/
public static final class TraceIterator {
public static final class TraceIterator implements Iterator<TraceEntry>{
private final TraceEntry[] entries;
/** Index as in LocationTrace */
private final int index;
@ -51,6 +61,7 @@ public class LocationTrace {
this.ascend = ascend;
}
@Override
public final TraceEntry next() {
if (!hasNext()) {
throw new IndexOutOfBoundsException("No more entries to iterate.");
@ -82,11 +93,17 @@ public class LocationTrace {
return entry;
}
@Override
public final boolean hasNext() {
// Just check if currentIndex is within range.
return currentIndex >= 0 && currentIndex <= index && currentIndex > index - size || currentIndex > index && currentIndex >= index - size + entries.length;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/** A Ring. */
@ -95,12 +112,13 @@ public class LocationTrace {
private int index = -1;
/** Number of valid entries. */
private int size = 0;
private final double mergeDist;
private final double mergeDistSq;
// (No world name stored: Should be reset on world changes.)
public LocationTrace(int bufferSize, double mergeDist) {
// TODO: Specify entry-merging conditions.
// TODO: Might consider a cut-off distance/age (performance saving for iteration).
if (bufferSize < 1) {
throw new IllegalArgumentException("Expect bufferSize > 0, got instead: " + bufferSize);
}
@ -108,19 +126,23 @@ public class LocationTrace {
for (int i = 0; i < bufferSize; i++) {
entries[i] = new TraceEntry();
}
this.mergeDist = mergeDist;
this.mergeDistSq = mergeDist * mergeDist;
}
public final void addEntry(final long time, final double x, final double y, final double z) {
// TODO: Consider setting the squared distance to last entry
double lastDistSq = 0.0;
if (size > 0) {
final TraceEntry oldEntry = entries[index];
final TraceEntry latestEntry = entries[index];
// TODO: Consider duration of staying there ?
if (x == oldEntry.x && y == oldEntry.y && z == oldEntry.z) {
oldEntry.time = time;
if (x == latestEntry.x && y == latestEntry.y && z == latestEntry.z) {
latestEntry.time = time;
return;
}
else if (TrigUtil.distanceSquared(x, y, z, oldEntry.x, oldEntry.y, oldEntry.z) <= mergeDistSq) {
lastDistSq = TrigUtil.distanceSquared(x, y, z, latestEntry.x, latestEntry.y, latestEntry.z);
// TODO: Think about minMergeSize (1 = never merge the first two, size = first fill the ring).
if (size > 1 && lastDistSq <= mergeDistSq) {
// TODO: Could use Manhattan, after all.
/**
* TODO: <br>
@ -128,8 +150,14 @@ public class LocationTrace {
* Introducing a mergeTime could also help against keeping too many outdated entries.<br>
* On merging conditions, checking dist/time vs. the second latest element could be feasible, supposedly with double distance. <br>
*/
oldEntry.set(time, x, y, z);
return;
// Only merge if last distance was not greater than mergeDist.
if (latestEntry.lastDistSq <= mergeDistSq) {
// Update lastDistSq, due to shifting the elements position.
final TraceEntry secondLatest = index - 1 < 0 ? entries[index - 1 + entries.length] : entries[index - 1];
lastDistSq = TrigUtil.distanceSquared(x, y, z, secondLatest.x, secondLatest.y, secondLatest.z);
latestEntry.set(time, x, y, z, lastDistSq);
return;
}
}
}
// Advance index.
@ -141,7 +169,7 @@ public class LocationTrace {
size ++;
}
final TraceEntry newEntry = entries[index];
newEntry.set(time, x, y, z);
newEntry.set(time, x, y, z, lastDistSq);
}
/** Reset content pointers - call with world changes. */
@ -150,14 +178,29 @@ public class LocationTrace {
size = 0;
}
/**
* Get the actual number of valid elements. After some time of moving this should be entries.length.
* @return
*/
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
/**
* Get size of ring buffer (maximal possible number of elements).
* @return
*/
public int getMaxSize() {
return entries.length;
}
public double getMergeDist() {
return mergeDist;
}
/**
* Iterate from latest to oldest.
* @return

View File

@ -137,10 +137,16 @@ public class MovingConfig extends ACheckConfig {
public final long sprintingGrace;
public final boolean assumeSprint;
public final int speedGrace;
public final boolean enforceLocation;
// Vehicles
public final boolean vehicleEnforceLocation;
public final boolean vehiclePreventDestroyOwn;
public final boolean enforceLocation;
// Trace
public final int traceSize;
public final double traceMergeDist;
/**
* Instantiates a new moving configuration.
@ -216,11 +222,14 @@ public class MovingConfig extends ACheckConfig {
sprintingGrace = Math.max(0L, (long) (config.getDouble(ConfPaths.MOVING_SPRINTINGGRACE) * 1000.0)); // Config: seconds.
assumeSprint = config.getBoolean(ConfPaths.MOVING_ASSUMESPRINT);
speedGrace = Math.max(0, (int) Math.round(config.getDouble(ConfPaths.MOVING_SPEEDGRACE) * 20.0)); // Config: seconds
enforceLocation = config.getBoolean(ConfPaths.MOVING_ENFORCELOCATION);
vehicleEnforceLocation = config.getBoolean(ConfPaths.MOVING_VEHICLES_ENFORCELOCATION);
vehiclePreventDestroyOwn = config.getBoolean(ConfPaths.MOVING_VEHICLES_PREVENTDESTROYOWN);
enforceLocation = config.getBoolean(ConfPaths.MOVING_ENFORCELOCATION);
traceSize = config.getInt(ConfPaths.MOVING_TRACE_SIZE);
traceMergeDist = config.getDouble(ConfPaths.MOVING_TRACE_MERGEDIST);
}

View File

@ -57,9 +57,13 @@ public class MovingData extends ACheckData {
* @return the data
*/
public static MovingData getData(final Player player) {
if (!playersMap.containsKey(player.getName()))
playersMap.put(player.getName(), new MovingData());
return playersMap.get(player.getName());
// Note that the trace might be null after just calling this.
MovingData data = playersMap.get(player.getName());
if (data == null) {
data = new MovingData();
playersMap.put(player.getName(), data);
}
return data;
}
public static ICheckData removeData(final String playerName) {
@ -81,6 +85,12 @@ public class MovingData extends ACheckData {
}
}
public static void onReload() {
for (final MovingData data : playersMap.values()) {
data.deleteTrace(); // Safe side.
}
}
/////////////////
// Not static.
/////////////////
@ -119,6 +129,8 @@ public class MovingData extends ACheckData {
public double fromX = Double.MAX_VALUE, fromY, fromZ;
/** Last to coordinates. */
public double toX = Double.MAX_VALUE, toY, toZ;
/** Moving trace (to positions). This is initialized on "playerJoins, i.e. MONITOR, and set to null on playerLeaves."*/
private LocationTrace trace = null;
// sf rather
/** To/from was ground or web or assumed to be etc. */
@ -645,6 +657,7 @@ public class MovingData extends ACheckData {
*/
public void onPlayerLeave() {
removeAllVelocity();
deleteTrace();
}
/**
@ -693,4 +706,67 @@ public class MovingData extends ACheckData {
}
}
/**
* This tests for a LocationTrace instance being set at all, not for locations having been added.
* @return
*/
public boolean hasTrace() {
return trace != null;
}
/**
* Convenience: Access method to simplify coding, being aware of some plugins using Player implementations as NPCs, leading to traces not being present.
* @return
*/
public LocationTrace getTrace(final Player player) {
if (trace == null) {
final MovingConfig cc = MovingConfig.getConfig(player);
trace = new LocationTrace(cc.traceSize, cc.traceMergeDist);
}
return trace;
}
/**
* Convenience
* @param player
* @param loc
*/
public void resetTrace(final Player player, final Location loc, final long time) {
final MovingConfig cc = MovingConfig.getConfig(player);
resetTrace(loc, time, cc.traceSize, cc.traceMergeDist);
}
/**
* Convenience method to add a location to the trace, creates the trace if necessary.
* @param player
* @param loc
* @param time
* @return Updated LocationTrace instance, for convenient use, without sticking too much to MovingData.
*/
public LocationTrace updateTrace(final Player player, final Location loc, final long time) {
final LocationTrace trace = getTrace(player);
trace.addEntry(time, loc.getX(), loc.getY(), loc.getZ());
return trace;
}
/**
* Convenience: Create or just reset the trace, add the current location.
* @param loc
* @param size
* @param mergeDist
* @param traceMergeDist
*/
public void resetTrace(final Location loc, final long time, final int size, double mergeDist) {
if (trace == null || trace.getMaxSize() != size || trace.getMergeDist() != mergeDist) {
trace = new LocationTrace(size, mergeDist);
} else {
trace.reset();
}
trace.addEntry(time, loc.getX(), loc.getY(), loc.getZ());
}
public void deleteTrace() {
trace = null;
}
}

View File

@ -318,6 +318,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
final Location loc = player.getLocation(useLoc);
data.setSetBack(loc);
data.resetPositions(loc);
data.resetTrace(loc, TickTask.getTick(), cc.traceSize, cc.traceMergeDist);
if (cc.enforceLocation) {
// Just in case.
playersEnforce.add(player.getName());
@ -701,12 +702,14 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// Feed combined check.
final CombinedData data = CombinedData.getData(player);
data.lastMoveTime = now;
data.lastMoveTime = now; // TODO: Evaluate moving this to MovingData !?
final Location from = event.getFrom();
final String fromWorldName = from.getWorld().getName();
// Feed yawrate and reset moving data positions if necessary.
final MovingData mData = MovingData.getData(player);
final long time = TickTask.getTick();
if (!event.isCancelled()) {
final Location to = event.getTo();
final String toWorldName = to.getWorld().getName();
@ -714,21 +717,26 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// TODO: maybe even not count vehicles at all ?
if (player.isInsideVehicle()) {
// TODO: refine (!).
MovingData.getData(player).resetPositions(player.getVehicle().getLocation(useLoc));
final Location ref = player.getVehicle().getLocation(useLoc);
mData.resetPositions(ref);
useLoc.setWorld(null);
mData.resetTrace(player, ref, time);
}
else if (!fromWorldName.equals(toWorldName)) {
MovingData.getData(player).resetPositions(to);
mData.resetPositions(to);
mData.resetTrace(player, to, time);
}
else{
// Slightly redundant at present.
MovingData.getData(player).setTo(to);
mData.setTo(to);
mData.resetTrace(player, to, time);
}
}
else {
// TODO: teleported + other resetting ?
Combined.feedYawRate(player, from.getYaw(), now, fromWorldName, data);
MovingData.getData(player).resetPositions(from);
mData.resetPositions(from);
mData.resetTrace(player, from, time); // TODO: Should probably leave this to the teleport event!
}
}
@ -1175,6 +1183,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
public void playerJoins(final Player player) {
final MovingData data = MovingData.getData(player);
final MovingConfig cc = MovingConfig.getConfig(player);
final int tick = TickTask.getTick();
// TODO: on existing set back: detect world changes and loss of world on join (+ set up some paradigm).
data.clearMorePacketsData();
data.removeAllVelocity();
@ -1193,6 +1202,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
// Always reset position to this one.
// TODO: more fine grained reset?
data.resetPositions(loc);
data.resetTrace(loc, tick, cc.traceSize, cc.traceMergeDist);
// More resetting.
data.vDistAcc.clear();
@ -1548,6 +1558,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
}
parkedInfo.clear();
hoverTicksStep = Math.max(1, ConfigManager.getConfigFile().getInt(ConfPaths.MOVING_SURVIVALFLY_HOVER_STEP));
MovingData.onReload();
}
}

View File

@ -574,6 +574,10 @@ public abstract class ConfPaths {
public static final String MOVING_VEHICLES_ENFORCELOCATION = MOVING_VEHICLES + "enforcelocation";
public static final String MOVING_VEHICLES_PREVENTDESTROYOWN = MOVING_VEHICLES + "preventdestroyown";
private static final String MOVING_TRACE = MOVING + "trace.";
public static final String MOVING_TRACE_SIZE = MOVING_TRACE + "size";
public static final String MOVING_TRACE_MERGEDIST = MOVING_TRACE + "mergedist";
public static final String STRINGS = "strings";

View File

@ -394,6 +394,10 @@ public class DefaultConfig extends ConfigFile {
set(ConfPaths.MOVING_SURVIVALFLY_HOVER_FALLDAMAGE, true);
set(ConfPaths.MOVING_SURVIVALFLY_HOVER_SFVIOLATION, 500);
// Moving Trace
set(ConfPaths.MOVING_TRACE_SIZE, 60);
set(ConfPaths.MOVING_TRACE_MERGEDIST, 0.9752); // Let all the hackers read code!
// Vehicles.
set(ConfPaths.MOVING_VEHICLES_PREVENTDESTROYOWN, true);
set(ConfPaths.MOVING_VEHICLES_ENFORCELOCATION, true);

View File

@ -66,9 +66,9 @@ public class TestLocationTrace {
@Test
public void testMergeUpdateAlwaysDist() {
// This assumes the last location is always updated.
// Extreme merge dist.
int size = 80;
double mergeDist = Math.sqrt(0.6 * 0.6 * 3);
double mergeDist = Math.sqrt(1000.0);
LocationTrace trace = new LocationTrace(size, mergeDist);
double x = 0;
double y = 0;
@ -79,13 +79,30 @@ public class TestLocationTrace {
y = rand(y, 0.5);
z = rand(z, 0.5);
trace.addEntry(i + 1, x, y, z);
if (trace.size() != 1) {
fail("Wrong size, expect 1, got instead: " + trace.size());
if (trace.size() != 2) {
fail("Wrong size, expect 2, got instead: " + trace.size());
}
}
}
// TODO: Test iterators.
@Test
public void testMergeDist() {
// Deterministic steps => calculatable size.
int size = 80;
double mergeDist = 0.5;
LocationTrace trace = new LocationTrace(size, mergeDist);
double x = 0;
double y = 0;
double z = 0;
trace.addEntry(0 , x, y, z);
for (int i = 0; i < size * 2; i ++) {
x += 0.5;
trace.addEntry(i + 1, x, y, z);
if (Math.abs(trace.size() - (1 + i / 2)) > 1 ) {
fail("Wrong size, expect roughly half of " + (i + 1) + ", got instead: " + trace.size());
}
}
}
@Test
public void testEmptyIterator() {
@ -174,6 +191,4 @@ public class TestLocationTrace {
}
}
// TODO: Iterator order for various iterators (!)
}