Skip to content

Commit

Permalink
[BLEEDING] Maintain location traces (not making use of those, yet).
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
asofold committed Mar 21, 2014
1 parent 520e7ba commit c27c03c
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 29 deletions.
@@ -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
*
*/
Expand All @@ -16,12 +24,14 @@ public static final class TraceEntry {
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;
}
}

Expand All @@ -30,7 +40,7 @@ public void set(long time, double x, double y, double z) {
* @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;
Expand All @@ -51,6 +61,7 @@ protected TraceIterator(TraceEntry[] entries, int index, int size, int currentIn
this.ascend = ascend;
}

@Override
public final TraceEntry next() {
if (!hasNext()) {
throw new IndexOutOfBoundsException("No more entries to iterate.");
Expand Down Expand Up @@ -82,11 +93,17 @@ public final TraceEntry next() {
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. */
Expand All @@ -95,41 +112,52 @@ public final boolean hasNext() {
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);
}
entries = new TraceEntry[bufferSize];
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>
* The last entry has to be up to date, but it can lead to a "stray entry" far off the second one on time.<br>
* 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.
Expand All @@ -141,7 +169,7 @@ else if (TrigUtil.distanceSquared(x, y, z, oldEntry.x, oldEntry.y, oldEntry.z) <
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. */
Expand All @@ -150,14 +178,29 @@ public void reset() {
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
Expand Down
Expand Up @@ -137,10 +137,16 @@ public static MovingConfig getConfig(final String worldName) {
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.
Expand Down Expand Up @@ -216,11 +222,14 @@ public MovingConfig(final ConfigFile config) {
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);

}


Expand Down
Expand Up @@ -57,9 +57,13 @@ public void removeAllData() {
* @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) {
Expand All @@ -81,6 +85,12 @@ public static void onWorldUnload(final World world) {
}
}

public static void onReload() {
for (final MovingData data : playersMap.values()) {
data.deleteTrace(); // Safe side.
}
}

/////////////////
// Not static.
/////////////////
Expand Down Expand Up @@ -119,6 +129,8 @@ public static void onWorldUnload(final World world) {
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. */
Expand Down Expand Up @@ -645,6 +657,7 @@ public boolean isSetBack(final Location loc) {
*/
public void onPlayerLeave() {
removeAllVelocity();
deleteTrace();
}

/**
Expand Down Expand Up @@ -693,4 +706,67 @@ public void adjustFlySpeed(final float flySpeed, final int tick, final int speed
}
}

/**
* 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;
}

}
Expand Up @@ -318,6 +318,7 @@ public void onPlayerChangedWorld(final PlayerChangedWorldEvent event) {
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());
Expand Down Expand Up @@ -701,34 +702,41 @@ public void onPlayerMoveMonitor(final PlayerMoveEvent event) {

// 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();
Combined.feedYawRate(player, to.getYaw(), now, toWorldName, data);
// 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!
}
}

Expand Down Expand Up @@ -1175,6 +1183,7 @@ public void onEntityDamage(final EntityDamageEvent event) {
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();
Expand All @@ -1193,6 +1202,7 @@ else if (!data.hasSetBack()) {
// 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();
Expand Down Expand Up @@ -1548,6 +1558,7 @@ public void onReload() {
}
parkedInfo.clear();
hoverTicksStep = Math.max(1, ConfigManager.getConfigFile().getInt(ConfPaths.MOVING_SURVIVALFLY_HOVER_STEP));
MovingData.onReload();
}

}

0 comments on commit c27c03c

Please sign in to comment.