NoCheatPlus/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingData.java

1364 lines
47 KiB
Java

/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.checks.moving;
import java.util.Collection;
import java.util.concurrent.Callable;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.access.ACheckData;
import fr.neatmonster.nocheatplus.checks.access.IRemoveSubCheckData;
import fr.neatmonster.nocheatplus.checks.moving.location.setback.DefaultSetBackStorage;
import fr.neatmonster.nocheatplus.checks.moving.location.tracking.LocationTrace;
import fr.neatmonster.nocheatplus.checks.moving.location.tracking.LocationTrace.TraceEntryPool;
import fr.neatmonster.nocheatplus.checks.moving.magic.Magic;
import fr.neatmonster.nocheatplus.checks.moving.model.LiftOffEnvelope;
import fr.neatmonster.nocheatplus.checks.moving.model.MoveConsistency;
import fr.neatmonster.nocheatplus.checks.moving.model.MoveTrace;
import fr.neatmonster.nocheatplus.checks.moving.model.PlayerMoveData;
import fr.neatmonster.nocheatplus.checks.moving.model.VehicleMoveData;
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.checks.moving.velocity.VelocityFlags;
import fr.neatmonster.nocheatplus.checks.workaround.WRPT;
import fr.neatmonster.nocheatplus.compat.blocks.changetracker.BlockChangeReference;
import fr.neatmonster.nocheatplus.components.entity.IEntityAccessDimensions;
import fr.neatmonster.nocheatplus.components.location.IGetPosition;
import fr.neatmonster.nocheatplus.components.location.IPositionWithLook;
import fr.neatmonster.nocheatplus.players.IPlayerData;
import fr.neatmonster.nocheatplus.utilities.CheckUtils;
import fr.neatmonster.nocheatplus.utilities.TickTask;
import fr.neatmonster.nocheatplus.utilities.ds.count.ActionAccumulator;
import fr.neatmonster.nocheatplus.utilities.ds.count.ActionFrequency;
import fr.neatmonster.nocheatplus.utilities.location.LocUtil;
import fr.neatmonster.nocheatplus.utilities.location.PlayerLocation;
import fr.neatmonster.nocheatplus.utilities.location.RichEntityLocation;
import fr.neatmonster.nocheatplus.utilities.location.TrigUtil;
import fr.neatmonster.nocheatplus.workaround.IWorkaroundRegistry.WorkaroundSet;
/**
* Player specific data for the moving checks.
*/
public class MovingData extends ACheckData implements IRemoveSubCheckData {
/*
* TODO: Handle world unload and other by registration (PlayerDataManager) -
* might just implement the interfaces and auto-handle at registration.
*/
// /**
// * Clear data related to the given world.
// * @param world The world that gets unloaded.
// */
// public static void onWorldUnload(final World world) {
// // TODO: Register with check (interfaces or just an event listener).
// final String worldName = world.getName();
// for (final MovingData data : playersMap.values()) {
// data.onWorldUnload(worldName);
// }
// }
//
// public static void onReload() {
// // TODO: Register with check (interfaces or just an event listener).
// final MovingConfig globalCc = MovingConfig.getConfig((String) null);
// final int tick = TickTask.getTick();
// for (final MovingData data : playersMap.values()) {
// data.adjustOnReload(globalCc, tick);
// }
// }
// Check specific.
/**
* Default lift-off envelope, used after resetting. <br>
* TODO: Test, might be better ground.
*/
private static final LiftOffEnvelope defaultLiftOffEnvelope = LiftOffEnvelope.UNKNOWN;
public static final int vehicleMorePacketsBufferDefault = 50;
/** 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.
/////////////////
// Violation levels -----
public double creativeFlyVL = 0.0;
public double morePacketsVL = 0.0;
public double noFallVL = 0.0;
public double survivalFlyVL = 0.0;
public double vehicleMorePacketsVL = 0.0;
public double vehicleEnvelopeVL = 0.0;
// Data shared between the fly checks -----
public int bunnyhopDelay;
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.
/** Compatibility entry for bouncing of slime blocks and the like. */
public SimpleEntry verticalBounce = null;
/** Last used block change id (BlockChangeTracker). */
public final BlockChangeReference blockChangeRef = new BlockChangeReference();
/** Tick at which walk/fly speeds got changed last time. */
public int speedTick = 0;
public float walkSpeed = 0.0f;
public float flySpeed = 0.0f;
/** Count set back (re-) setting. */
private int playerMoveCount = 0;
/**
* setBackResetCount (incremented) at the time of (re-) setting the ordinary
* set back.
*/
private int setBackResetTime = 0;
/**
* setBackResetCount (incremented) at the time of (re-) setting the
* morepackets set back.
*/
private int morePacketsSetBackResetTime = 0;
/**
* Position teleported from into another world. Only used for certain
* contexts for workarounds.
*/
public IPositionWithLook crossWorldFrom = null;
/** Keep track of currently processed (if) and past moves for player moving. */
public final MoveTrace <PlayerMoveData> playerMoves = new MoveTrace<PlayerMoveData>(new Callable<PlayerMoveData>() {
@Override
public PlayerMoveData call() throws Exception {
return new PlayerMoveData();
}
}, 2);
/** Keep track of currently processed (if) and past moves for vehicle moving. */
// TODO: There may be need to store such data with vehicles, or detect tandem abuse in a different way.
public final MoveTrace <VehicleMoveData> vehicleMoves = new MoveTrace<VehicleMoveData>(new Callable<VehicleMoveData>() {
@Override
public VehicleMoveData call() throws Exception {
return new VehicleMoveData();
}
}, 2);
// Velocity handling.
/** Vertical velocity modeled as an axis (positive and negative possible) */
private final SimpleAxisVelocity verVel = new SimpleAxisVelocity();
/** Horizontal velocity modeled as an axis (always positive) */
private final FrictionAxisVelocity horVel = new FrictionAxisVelocity();
/** Duration of the boost effect in ticks. */
public int fireworksBoostDuration = 0;
/**
* Expire at this tick.
*/
public int fireworksBoostTickExpire = 0;
// Coordinates.
/** Moving trace (to-positions, use tick as time). This is initialized on "playerJoins, i.e. MONITOR, and set to null on playerLeaves." */
private final LocationTrace trace;
// sf rather
/** Basic envelope constraints for switching into air. */
public LiftOffEnvelope liftOffEnvelope = defaultLiftOffEnvelope;
/** Count how many moves have been made inside a medium (other than air). */
public int insideMediumCount = 0;
// TODO: Does combinedMedium stuff need resetting on join/teleport/special?
/** Number of moves for horizontal moving within air + certain medium. */
public int combinedMediumHCount = 0;
/** Sum of actual speed / base speed for horizontal moving within air + certain medium. */
public double combinedMediumHValue = 0.0;
// Locations shared between all checks.
private Location setBack = null;
private Location teleported = null;
// Check specific data -----
// Data of the more packets check.
/** Packet frequency count. */
public final ActionFrequency morePacketsFreq;
/** Burst count. */
public final ActionFrequency morePacketsBurstFreq;
private Location morePacketsSetback = null;
// Data of the no fall check.
public float noFallFallDistance = 0;
/** Last y coordinate from when the player was on ground. */
public double noFallMaxY = 0;
/** Indicate that NoFall is not to use next damage event for checking on-ground properties. */
public boolean noFallSkipAirCheck = false;
// Passable check.
public double passableVL;
// Data of the survival fly check.
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;
/**
* Count how many times in a row v-dist has been zero, only for in-air
* moves, updated on not cancelled moves (aimed at in-air workarounds).
*/
public int sfZeroVdistRepeat = 0;
/** Only used during processing, to keep track of sub-checks using velocity. Reset in velocityTick, before checks run. */
/** "Dirty" flag, for receiving velocity and similar while in air. */
private boolean sfDirty = false;
/** Indicate low jumping descending phase (likely cheating). */
public boolean sfLowJump = false;
public boolean sfNoLowJump = false; // Hacks.
/**
* 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.
*/
public int sfHoverLoginTicks = 0;
public int sfOnIce = 0; // TODO: Replace by allowed speed + friction.
public long sfCobwebTime = 0;
public double sfCobwebVL = 0;
public long sfVLTime = 0;
// Accounting info.
public final ActionAccumulator vDistAcc = new ActionAccumulator(3, 3);
/**
* Rough friction factor estimate, 0.0 is the reset value (maximum with
* lift-off/burst speed is used).
*/
public double lastFrictionHorizontal = 0.0;
/**
* Rough friction factor estimate, 0.0 is the reset value (maximum with
* lift-off/burst speed is used).
*/
public double lastFrictionVertical = 0.0;
/** Used during processing, no resetting necessary.*/
public double nextFrictionHorizontal = 0.0;
/** Used during processing, no resetting necessary.*/
public double nextFrictionVertical= 0.0;
/** Workarounds */
public final WorkaroundSet ws;
// HOT FIX / WORKAROUND
/**
* Set to true after login/respawn, only if the set back is reset there.
* Reset in MovingListener after handling PlayerMoveEvent
*/
public boolean joinOrRespawn = false;
/**
* Number of (player/vehicle) move events since set.back. Update after
* running standard checks on that EventPriority level (not MONITOR).
*/
public int timeSinceSetBack = 0;
/**
* Location hash value of the last (player/vehicle) set back, for checking
* independently of which set back location had been used.
*/
public int lastSetBackHash = 0;
// Vehicles.
/**
* Inconsistency-flag. Set on moving inside of vehicles, reset on exiting
* properly. Workaround for VehicleLeaveEvent missing.
*/
public boolean wasInVehicle = false; // Workaround
/**
* Set to indicate that events happen during a vehicle set back. Allows
* skipping some resetting.
*/
public boolean isVehicleSetBack = false; // Workaround
public MoveConsistency vehicleConsistency = MoveConsistency.INCONSISTENT; // Workaround
public final DefaultSetBackStorage vehicleSetBacks = new DefaultSetBackStorage();
// Data of the more packets vehicle check.
public int vehicleMorePacketsBuffer = vehicleMorePacketsBufferDefault;
public long vehicleMorePacketsLastTime;
/** Task id of the vehicle set back task. */
public int vehicleSetBackTaskId = -1;
private final IPlayerData pData;
public MovingData(final MovingConfig config, final IPlayerData pData) {
this.pData = pData;
morePacketsFreq = new ActionFrequency(config.morePacketsEPSBuckets, 500);
morePacketsBurstFreq = new ActionFrequency(12, 5000);
// Location trace.
trace = new LocationTrace(config.traceMaxAge, config.traceMaxSize, NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(TraceEntryPool.class));
// A new set of workaround conters.
ws = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(WRPT.class).getWorkaroundSet(WRPT.WS_MOVING);
}
@Override
public boolean removeSubCheckData(final CheckType checkType) {
// TODO: LocationTrace stays (leniency for other players!).
// TODO: Likely more fields left to change.
switch (checkType) {
/*
* TODO: case MOVING: // Remove all in-place (future: data might
* stay as long as the player is online).
*/
case MOVING_SURVIVALFLY:
survivalFlyVL = 0;
clearFlyData();
resetSetBack(); // TODO: Not sure this is really best for compatibility.
// TODO: other?
return true;
case MOVING_CREATIVEFLY:
creativeFlyVL = 0;
clearFlyData();
resetSetBack(); // TODO: Not sure this is really best for compatibility.
// TODO: other?
return true;
case MOVING_NOFALL:
noFallVL = 0;
clearNoFallData();
return true;
case MOVING_MOREPACKETS:
morePacketsVL = 0;
clearPlayerMorePacketsData();
morePacketsSetback = null;
morePacketsSetBackResetTime = 0;
return true;
case MOVING_PASSABLE:
passableVL = 0;
return true;
case MOVING_VEHICLE:
vehicleEnvelopeVL = 0;
vehicleMorePacketsVL = 0;
clearVehicleData();
return true;
case MOVING_VEHICLE_ENVELOPE:
vehicleEnvelopeVL = 0;
vehicleMoves.invalidate();
vehicleSetBacks.invalidateAll(); // Also invalidates morepackets set back.
return true;
case MOVING_VEHICLE_MOREPACKETS:
vehicleMorePacketsVL = 0;
clearVehicleMorePacketsData();
return true;
default:
return false;
}
}
/**
* Clear fly and more packets check data for both vehicles and players.
*/
public void clearMostMovingCheckData() {
clearFlyData();
clearVehicleData();
clearAllMorePacketsData();
}
/**
* Clear vehicle related data, except more packets.
*/
public void clearVehicleData() {
// TODO: Not entirely sure what to do here.
vehicleMoves.invalidate();
vehicleSetBacks.invalidateAll();
}
/**
* Clear the data of the fly checks (not more-packets).
*/
public void clearFlyData() {
playerMoves.invalidate();
bunnyhopDelay = 0;
sfJumpPhase = 0;
jumpAmplifier = 0;
setBack = null;
sfZeroVdistRepeat = 0;
clearAccounting();
clearNoFallData();
removeAllPlayerSpeedModifiers();
lostSprintCount = 0;
sfHoverTicks = sfHoverLoginTicks = -1;
sfDirty = false;
sfLowJump = false;
liftOffEnvelope = defaultLiftOffEnvelope;
insideMediumCount = 0;
vehicleConsistency = MoveConsistency.INCONSISTENT;
lastFrictionHorizontal = lastFrictionVertical = 0.0;
verticalBounce = null;
blockChangeRef.valid = false;
}
/**
* On confirming a set back (teleport monitor / move start point): Mildly
* reset the flying data without losing any important information. Past move
* is adjusted to the given setBack, internal setBack is only updated, if
* none is set.
*
* @param setBack
*/
public void onSetBack(final PlayerLocation setBack) {
// Reset positions (a teleport should follow, though).
this.morePacketsSetback = null;
clearAccounting(); // Might be more safe to do this.
// Keep no-fall data.
// Fly data: problem is we don't remember the settings for the set back location.
// Assume the player to start falling from there rather, or be on ground.
// TODO: Check if to adjust some counters to state before setback?
// Keep jump amplifier
// Keep bunny-hop delay (?)
// keep jump phase.
lostSprintCount = 0;
sfHoverTicks = -1; // 0 ?
sfDirty = false;
sfLowJump = false;
liftOffEnvelope = defaultLiftOffEnvelope;
insideMediumCount = 0;
removeAllPlayerSpeedModifiers();
vehicleConsistency = MoveConsistency.INCONSISTENT; // Not entirely sure here.
lastFrictionHorizontal = lastFrictionVertical = 0.0;
verticalBounce = null;
timeSinceSetBack = 0;
lastSetBackHash = setBack == null ? 0 : setBack.hashCode();
// Reset to setBack.
resetPlayerPositions(setBack);
adjustMediumProperties(setBack);
// Only setSetBack if no set back location is there.
if (setBack == null) {
setSetBack(setBack);
}
// vehicleSetBacks.resetAllLazily(setBack); // Not good: Overrides older set back locations.
}
/**
* Move event: Mildly reset some data, prepare setting a new to-Location.
*/
public void prepareSetBack(final Location loc) {
playerMoves.invalidate();
vehicleMoves.invalidate();
clearAccounting();
sfJumpPhase = 0;
sfZeroVdistRepeat = 0;
verticalBounce = null;
// Remember where we send the player to.
setTeleported(loc);
// TODO: sfHoverTicks ?
}
/**
* Adjust properties that relate to the medium, called on set back and
* similar. <br>
* Currently: liftOffEnvelope, nextFriction.
*
* @param loc
*/
public void adjustMediumProperties(final PlayerLocation loc) {
// Ensure block flags have been collected.
loc.collectBlockFlags();
// Simplified.
if (loc.isInWeb()) {
liftOffEnvelope = LiftOffEnvelope.NO_JUMP;
nextFrictionHorizontal = nextFrictionVertical = 0.0;
}
else if (loc.isInLiquid()) {
// TODO: Distinguish strong limit.
liftOffEnvelope = LiftOffEnvelope.LIMIT_LIQUID;
if (loc.isInLava()) {
nextFrictionHorizontal = nextFrictionVertical = Magic.FRICTION_MEDIUM_LAVA;
} else {
nextFrictionHorizontal = nextFrictionVertical = Magic.FRICTION_MEDIUM_WATER;
}
}
else if (loc.isOnGround()) {
liftOffEnvelope = LiftOffEnvelope.NORMAL;
nextFrictionHorizontal = nextFrictionVertical = Magic.FRICTION_MEDIUM_AIR;
}
else {
liftOffEnvelope = LiftOffEnvelope.UNKNOWN;
nextFrictionHorizontal = nextFrictionVertical = Magic.FRICTION_MEDIUM_AIR;
}
insideMediumCount = 0;
}
/**
* Called when a player leaves the server.
*/
public void onPlayerLeave() {
removeAllPlayerSpeedModifiers();
trace.reset();
playerMoves.invalidate();
vehicleMoves.invalidate();
}
/**
* Clean up data related to worlds with the given name (not case-sensitive).
* @param worldName
*/
public void onWorldUnload(final String worldName) {
// TODO: Unlink world references.
if (teleported != null && worldName.equalsIgnoreCase(teleported.getWorld().getName())) {
resetTeleported();
}
if (setBack != null && worldName.equalsIgnoreCase(setBack.getWorld().getName())) {
clearFlyData();
}
if (morePacketsSetback != null && worldName.equalsIgnoreCase(morePacketsSetback.getWorld().getName())) {
clearPlayerMorePacketsData();
clearNoFallData(); // just in case.
}
// (Assume vehicle data needn't really reset here.)
vehicleSetBacks.resetByWorldName(worldName);
}
/**
* Invalidate all past player moves data and set last position if not null.
*
* @param loc
*/
public void resetPlayerPositions(final PlayerLocation loc) {
resetPlayerPositions();
if (loc != null) {
final PlayerMoveData lastMove = playerMoves.getFirstPastMove();
// Always set with extra properties.
lastMove.setWithExtraProperties(loc);
}
}
/**
* Invalidate all past moves data (player).
*/
private void resetPlayerPositions() {
playerMoves.invalidate();
sfZeroVdistRepeat = 0;
sfDirty = false;
sfLowJump = false;
liftOffEnvelope = defaultLiftOffEnvelope;
insideMediumCount = 0;
lastFrictionHorizontal = lastFrictionVertical = 0.0;
verticalBounce = null;
blockChangeRef.valid = false;
// TODO: other buffers ?
// No reset of vehicleConsistency.
}
/**
* Invalidate all past vehicle moves data and set last position if not null.
*
* @param loc
*/
public void resetVehiclePositions(final RichEntityLocation loc) {
// TODO: Other properties (convenience, e.g. set back?) ?
vehicleMoves.invalidate();
if (loc != null) {
final VehicleMoveData lastMove = vehicleMoves.getFirstPastMove();
// Always set with extra properties.
lastMove.setWithExtraProperties(loc);
final Entity entity = loc.getEntity();
lastMove.vehicleId = entity.getUniqueId();
lastMove.vehicleType = entity.getType();
}
}
/**
* Clear accounting data.
*/
public void clearAccounting() {
vDistAcc.clear();
}
/**
* Clear the data of the more packets checks, both for players and vehicles.
*/
public void clearAllMorePacketsData() {
clearPlayerMorePacketsData();
clearVehicleMorePacketsData();
}
public void clearPlayerMorePacketsData() {
morePacketsSetback = null;
final long now = System.currentTimeMillis();
morePacketsFreq.clear(now);
morePacketsBurstFreq.clear(now);
// TODO: Also reset other data ?
}
/**
* Reduce the morepackets frequency counters by the given amount, capped at
* a minimum of 0.
*
* @param amount
*/
public void reducePlayerMorePacketsData(final float amount) {
ActionFrequency.reduce(System.currentTimeMillis(), amount, morePacketsFreq, morePacketsBurstFreq);
}
public void clearVehicleMorePacketsData() {
vehicleMorePacketsLastTime = 0;
vehicleMorePacketsBuffer = vehicleMorePacketsBufferDefault;
vehicleSetBacks.getMidTermEntry().setValid(false); // TODO: Will have other resetting conditions later on.
// TODO: Also reset other data ?
}
/**
* Clear the data of the new fall check.
*/
public void clearNoFallData() {
noFallFallDistance = 0;
noFallMaxY = 0.0;
noFallSkipAirCheck = false;
}
/**
* Set the set back location, this will also adjust the y-coordinate for some block types (at least air).
* @param loc
*/
public void setSetBack(final PlayerLocation loc) {
if (setBack == null) {
setBack = loc.getLocation();
}
else{
LocUtil.set(setBack, loc);
}
// TODO: Consider adjusting the set back-y here. Problem: Need to take into account for bounding box (collect max-ground-height needed).
setBackResetTime = playerMoveCount;
}
/**
* Convenience method.
* @param loc
*/
public void setSetBack(final Location loc) {
if (setBack == null) {
setBack = LocUtil.clone(loc);
}
else{
LocUtil.set(setBack, loc);
}
setBackResetTime = playerMoveCount;
}
/**
* Get the set back location with yaw and pitch set form ref.
* @param ref
* @return
*/
public Location getSetBack(final Location ref) {
return LocUtil.clone(setBack, ref);
}
/**
* Get the set back location with yaw and pitch set from ref.
* @param ref
* @return
*/
public Location getSetBack(final PlayerLocation ref) {
return LocUtil.clone(setBack, ref);
}
public boolean hasSetBack() {
return setBack != null;
}
public boolean hasSetBackWorldChanged(final Location loc) {
if (setBack == null) {
return true;
}
else {
return setBack.getWorld().equals(loc.getWorld());
}
}
public double getSetBackX() {
return setBack.getX();
}
public double getSetBackY() {
return setBack.getY();
}
public double getSetBackZ() {
return setBack.getZ();
}
public void setSetBackY(final double y) {
setBack.setY(y);
// (Skip setting/increasing the reset count.)
}
/**
* Test, if the 'teleported' location is set, e.g. on a scheduled set back.
*
* @return
*/
public boolean hasTeleported() {
return teleported != null;
}
/**
* Return a copy of the teleported-to Location.
* @return
*/
public final Location getTeleported() {
// TODO: here a reference might do.
return teleported == null ? teleported : LocUtil.clone(teleported);
}
/**
* Check if the given location equals to the 'teleported' (set back)
* location.
*
* @param loc
* @return In case of either loc or teleported being null, false is
* returned, otherwise teleported.equals(loc).
*/
public boolean isTeleported(final Location loc) {
return loc != null && teleported != null && teleported.equals(loc);
}
/**
* Check if the given location has the same coordinates like the
* 'teleported' (set back) location. This is more light-weight and more
* lenient than isTeleported, because world and yaw and pitch are all
* ignored.
*
* @param loc
* @return In case of either loc or teleported being null, false is
* returned, otherwise TrigUtil.isSamePos(teleported, loc).
*/
public boolean isTeleportedPosition(final Location loc) {
return loc != null && teleported != null && TrigUtil.isSamePos(teleported, loc);
}
/**
* Check if the given location has the same coordinates like the
* 'teleported' (set back) location. This is more light-weight and more
* lenient than isTeleported, because world and yaw and pitch are all
* ignored.
*
* @param loc
* @return In case of either loc or teleported being null, false is
* returned, otherwise TrigUtil.isSamePos(pos, teleported).
*/
public boolean isTeleportedPosition(final IGetPosition pos) {
return pos != null && teleported != null && TrigUtil.isSamePos(pos, teleported);
}
/**
* Set teleport-to location to recognize NCP set backs. This copies the coordinates and world.
* @param loc
*/
public final void setTeleported(final Location loc) {
teleported = LocUtil.clone(loc); // Always overwrite.
}
public boolean hasMorePacketsSetBack() {
return morePacketsSetback != null;
}
/**
* Test if the morepackets set back is older than the ordinary set back.
* Does not check for existence of either.
*
* @return
*/
public boolean isMorePacketsSetBackOldest() {
return morePacketsSetBackResetTime < setBackResetTime;
}
public final void setMorePacketsSetBack(final PlayerLocation loc) {
if (morePacketsSetback == null) {
morePacketsSetback = loc.getLocation();
}
else {
LocUtil.set(morePacketsSetback, loc);
}
morePacketsSetBackResetTime = playerMoveCount;
}
public final void setMorePacketsSetBack(final Location loc) {
if (morePacketsSetback == null) {
morePacketsSetback = LocUtil.clone(loc);
}
else {
LocUtil.set(morePacketsSetback, loc);
}
morePacketsSetBackResetTime = playerMoveCount;
}
public Location getMorePacketsSetBack() {
return LocUtil.clone(morePacketsSetback);
}
public final void resetTeleported() {
teleported = null;
}
/**
* Set set back location to null.
*/
public final void resetSetBack() {
setBack = 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) {
addVelocity(player, cc, vx, vy, vz, 0L);
}
/**
* Add velocity to internal book-keeping.
*
* @param player
* @param data
* @param cc
* @param vx
* @param vy
* @param vz
* @param flags
* Flags to use with velocity entries.
*/
public void addVelocity(final Player player, final MovingConfig cc,
final double vx, final double vy, final double vz, final long flags) {
final int tick = TickTask.getTick();
// TODO: Slightly odd to call this each time, might switch to a counter-strategy (move - remove).
removeInvalidVelocity(tick - cc.velocityActivationTicks);
if (pData.isDebugActive(CheckType.MOVING)) {
CheckUtils.debug(player, CheckType.MOVING, "New velocity: " + vx + ", " + vy + ", " + vz);
}
// Always add vertical velocity.
verVel.add(new SimpleEntry(tick, vy, flags, 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, getHorVelValCount(newVal)));
}
// Set dirty flag here.
sfDirty = true; // TODO: Set on using the velocity, due to latency !
sfNoLowJump = true; // TODO: Set on using the velocity, due to latency !
}
/**
* Std. value counter for horizontal velocity, based on the vlaue.
*
* @param velocity
* @return
*/
public static int getHorVelValCount(double velocity) {
return Math.max(20, 1 + (int) Math.round(velocity * 10.0));
}
public void prependVerticalVelocity(final SimpleEntry entry) {
verVel.addToFront(entry);
}
/**
* Get the first element without using it.
* @param amount
* @param minActCount
* @param maxActCount
* @return
*/
public SimpleEntry peekVerticalVelocity(final double amount, final int minActCount, final int maxActCount) {
return verVel.peek(amount, minActCount, maxActCount, TOL_VVEL);
}
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);
}
/**
* Remove/reset all speed modifier tracking, like vertical and horizontal
* velocity, elytra boost, buffer.
*/
private void removeAllPlayerSpeedModifiers() {
// Velocity
removeAllVelocity();
// Elytra boost best fits velocity / effects.
fireworksBoostDuration = 0;
fireworksBoostTickExpire = 0;
// Horizontal buffer.
sfHorizontalBuffer = 0.0;
}
/**
* Reset velocity tracking (h+v).
*/
public void removeAllVelocity() {
horVel.clear();
verVel.clear();
sfDirty = false;
}
/**
* Remove all velocity entries that are invalid. Checks both active and queued.
* <br>(This does not catch invalidation by speed / direction changing.)
* @param tick All velocity added before this tick gets removed.
*/
public void removeInvalidVelocity(final int tick) {
horVel.removeInvalid(tick);
verVel.removeInvalid(tick);
}
/**
* Clear only active horizontal velocity.
*/
public void clearActiveHorVel() {
horVel.clearActive();
}
public boolean hasActiveHorVel() {
return horVel.hasActive();
}
public boolean hasQueuedHorVel() {
return horVel.hasQueued();
}
/**
* Active or queued.
* @return
*/
public boolean hasAnyHorVel() {
return horVel.hasAny();
}
/**
* Queued velocity only.
* @return
*/
public boolean hasAnyVerVel() {
return verVel.hasQueued();
}
// public boolean hasActiveVerVel() {
// return verVel.hasActive();
// }
// public boolean hasQueuedVerVel() {
// return verVel.hasQueued();
// }
/**
* 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(final int invalidateBeforeTick) {
// Remove invalid velocity.
removeInvalidVelocity(invalidateBeforeTick);
// Horizontal velocity (intermediate concept).
horVel.tick();
// (Vertical velocity does not tick.)
// Renew the dirty phase.
if (!sfDirty && (horVel.hasActive() || horVel.hasQueued())) {
sfDirty = true;
}
}
/**
* Get effective amount of all used velocity. Non-destructive.
* @return
*/
public double getHorizontalFreedom() {
return horVel.getFreedom();
}
/**
* 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 demanded, must be positive.
* @return
*/
public double useHorizontalVelocity(final double amount) {
final double available = horVel.use(amount);
if (available >= amount) {
sfDirty = true;
}
return available;
}
/**
* Debugging.
* @param builder
*/
public void addHorizontalVelocity(final StringBuilder builder) {
if (horVel.hasActive()) {
builder.append("\n" + " horizontal velocity (active):");
horVel.addActive(builder);
}
if (horVel.hasQueued()) {
builder.append("\n" + " horizontal velocity (queued):");
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) {
playerMoves.getCurrentMove().verVelUsed = available;
sfDirty = true;
// TODO: Consider sfNoLowJump = true;
}
return available;
}
/**
* Use the verVelUsed field, if it matches. Otherwise call
* useVerticalVelocity(amount).
*
* @param amount
* @return
*/
public SimpleEntry getOrUseVerticalVelocity(final double amount) {
final SimpleEntry verVelUsed = playerMoves.getCurrentMove().verVelUsed;
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);
}
}
/**
* Test if the location is the same, ignoring pitch and yaw.
* @param loc
* @return
*/
public boolean isSetBack(final Location loc) {
if (loc == null || setBack == null) {
return false;
}
if (!loc.getWorld().getName().equals(setBack.getWorld().getName())) {
return false;
}
return loc.getX() == setBack.getX() && loc.getY() == setBack.getY() && loc.getZ() == setBack.getZ();
}
public void adjustWalkSpeed(final float walkSpeed, final int tick, final int speedGrace) {
if (walkSpeed > this.walkSpeed) {
this.walkSpeed = walkSpeed;
this.speedTick = tick;
} else if (walkSpeed < this.walkSpeed) {
if (tick - this.speedTick > speedGrace) {
this.walkSpeed = walkSpeed;
this.speedTick = tick;
}
} else {
this.speedTick = tick;
}
}
public void adjustFlySpeed(final float flySpeed, final int tick, final int speedGrace) {
if (flySpeed > this.flySpeed) {
this.flySpeed = flySpeed;
this.speedTick = tick;
} else if (flySpeed < this.flySpeed) {
if (tick - this.speedTick > speedGrace) {
this.flySpeed = flySpeed;
this.speedTick = tick;
}
} else {
this.speedTick = tick;
}
}
/**
* 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) {
return trace;
}
/**
* Ensure to have a LocationTrace instance with the given parameters.
*
* @param maxAge
* @param maxSize
* @return
*/
private LocationTrace getTrace(final int maxAge, final int maxSize) {
if (trace.getMaxSize() != maxSize || trace.getMaxAge() != maxAge) {
// TODO: Might want to have tick passed as argument?
trace.adjustSettings(maxAge, maxSize, TickTask.getTick());
}
return trace;
}
/**
* Convenience method to add a location to the trace, creates the trace if
* necessary.
*
* @param player
* @param loc
* @param time
* @param iead
* If null getEyeHeight and 0.3 are used (assume fake player).
* @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 IEntityAccessDimensions iead) {
final LocationTrace trace = getTrace(player);
if (iead == null) {
// TODO: 0.3 from bukkit based default heights (needs extra registered classes).
trace.addEntry(time, loc.getX(), loc.getY(), loc.getZ(), 0.3, player.getEyeHeight());
}
else {
trace.addEntry(time, loc.getX(), loc.getY(), loc.getZ(), iead.getWidth(player) / 2.0, Math.max(player.getEyeHeight(), iead.getHeight(player)));
}
return trace;
}
/**
* Convenience.
* @param loc
* @param time
* @param cc
*/
public void resetTrace(final Player player, final Location loc, final long time,
final IEntityAccessDimensions iead, final MovingConfig cc) {
resetTrace(player, loc, time, cc.traceMaxAge, cc.traceMaxSize, iead);
}
/**
* Convenience: Create or just reset the trace, add the current location.
* @param loc
* @param size
* @param mergeDist
* @param traceMergeDist
*/
public void resetTrace(final Player player, final Location loc, final long time,
final int maxAge, final int maxSize, final IEntityAccessDimensions iead) {
if (trace != null) {
trace.reset();
}
getTrace(maxAge, maxSize).addEntry(time, loc.getX(), loc.getY(), loc.getZ(),
iead.getWidth(player) / 2.0, Math.max(player.getEyeHeight(), iead.getHeight(player)));
}
/**
* Adjust to new parameters.
*/
protected void adjustOnReload(final MovingConfig cc, final int tick) {
trace.adjustSettings(cc.traceMaxAge, cc.traceMaxSize, tick);
}
/**
* Test if velocity has affected the in-air jumping phase. Keeps set until
* reset on-ground or otherwise. Use clearActiveVerVel to force end velocity
* jump phase. Use hasAnyVerVel() to test if active or queued vertical
* velocity should still be able to influence the in-air jump phase.
*
* @return
*/
public boolean isVelocityJumpPhase() {
return sfDirty;
}
/**
* Refactoring stage: Test which value sfDirty should have and set
* accordingly. This should only be called, if the player reached ground.
*
* @return If the velocity jump phase is still active (sfDirty).
*/
public boolean resetVelocityJumpPhase() {
return resetVelocityJumpPhase(null);
}
/**
* See {@link #resetVelocityJumpPhase()}.
* @param tags
* @return
*/
public boolean resetVelocityJumpPhase(final Collection<String> tags) {
if (horVel.hasActive() || horVel.hasQueued()
|| sfDirty && shouldRetainSFDirty(tags)) {
// TODO: What with vertical ?
return sfDirty = true;
}
else {
return sfDirty = false;
}
}
private final boolean shouldRetainSFDirty(final Collection<String> tags) {
final PlayerMoveData thisMove = playerMoves.getLatestValidMove();
if (thisMove == null || !thisMove.toIsValid || thisMove.yDistance >= 0.0) {
final SimpleEntry entry = verVel.peek(
thisMove == null ? 0.05 : thisMove.yDistance, 0, 4, 0.0);
if (entry != null && entry.hasFlag(VelocityFlags.ORIGIN_BLOCK_BOUNCE)
|| thisMove != null && thisMove.verVelUsed != null
&& thisMove.verVelUsed.hasFlag(VelocityFlags.ORIGIN_BLOCK_BOUNCE)) {
// TODO: Strictly, pastground_from/to should rather be skipped instead of this.
if (tags != null) {
tags.add("retain_dirty_bounce"); // +- block/push
}
return true;
}
}
return false;
}
/**
* Force set the move to be affected by previous speed. Currently
* implemented as setting velocity jump phase.
*/
public void setFrictionJumpPhase() {
// TODO: Better and more reliable modeling.
sfDirty = true;
}
public void useVerticalBounce(final Player player) {
// CHEATING: Ensure fall distance is reset.
player.setFallDistance(0f);
noFallMaxY = 0.0;
noFallFallDistance = 0f;
noFallSkipAirCheck = true;
prependVerticalVelocity(verticalBounce);
verticalBounce = null;
}
public void handleTimeRanBackwards() {
final long time = System.currentTimeMillis();
timeSprinting = Math.min(timeSprinting, time);
vehicleMorePacketsLastTime = Math.min(vehicleMorePacketsLastTime, time);
sfCobwebTime = Math.min(sfCobwebTime, time);
sfVLTime = Math.min(sfVLTime, time);
clearAccounting(); // Not sure: adding up might not be nice.
removeAllPlayerSpeedModifiers(); // TODO: This likely leads to problems.
// (ActionFrequency can handle this.)
}
/**
* Get the y-axis velocity tracker. Rather for testing purposes.
*
* @return
*/
public SimpleAxisVelocity getVerticalVelocityTracker() {
return verVel;
}
/**
* The number of move events received.
*
* @return
*/
public int getPlayerMoveCount() {
return playerMoveCount;
}
/**
* Called with player move events.
*/
public void increasePlayerMoveCount() {
playerMoveCount++;
if (playerMoveCount == Integer.MAX_VALUE) {
playerMoveCount = 0;
morePacketsSetBackResetTime = 0;
setBackResetTime = 0;
}
}
/**
* Age in move events.
* @return
*/
public int getMorePacketsSetBackAge() {
return playerMoveCount - morePacketsSetBackResetTime;
}
/**
* Remove from start while the flag is present.
* @param originBlockBounce
*/
public void removeLeadingQueuedVerticalVelocityByFlag(final long flag) {
verVel.removeLeadingQueuedVerticalVelocityByFlag(flag);
}
}