Spaces + comments, modifier.

This commit is contained in:
asofold 2014-11-24 15:17:04 +01:00
parent 8b55139003
commit 24ccc4a5ea
9 changed files with 866 additions and 867 deletions

View File

@ -32,12 +32,12 @@ public class Direction extends Check {
* @return true, if successful
*/
public boolean check(final Player player, final Location loc, final Entity damaged, final Location dLoc, final FightData data, final FightConfig cc) {
boolean cancel = false;
boolean cancel = false;
// Safeguard, if entity is complex, this check will fail due to giant and hard to define hitboxes.
// if (damaged instanceof EntityComplex || damaged instanceof EntityComplexPart)
// if (damaged instanceof EntityComplex || damaged instanceof EntityComplexPart)
if (mcAccess.isComplexPart(damaged)) {
return false;
return false;
}
// Find out how wide the entity is.
@ -45,20 +45,20 @@ public class Direction extends Check {
// entity.height is broken and will always be 0, therefore. Calculate height instead based on boundingBox.
final double height = mcAccess.getHeight(damaged);
// TODO: allow any hit on the y axis (might just adapt interface to use foot position + height)!
// How far "off" is the player with their aim. We calculate from the players eye location and view direction to
// the center of the target entity. If the line of sight is more too far off, "off" will be bigger than 0.
final Vector direction = loc.getDirection();
final double off;
if (cc.directionStrict){
off = TrigUtil.combinedDirectionCheck(loc, player.getEyeHeight(), direction, dLoc.getX(), dLoc.getY() + height / 2D, dLoc.getZ(), width, height, TrigUtil.DIRECTION_PRECISION, 80.0);
off = TrigUtil.combinedDirectionCheck(loc, player.getEyeHeight(), direction, dLoc.getX(), dLoc.getY() + height / 2D, dLoc.getZ(), width, height, TrigUtil.DIRECTION_PRECISION, 80.0);
}
else{
// Also take into account the angle.
off = TrigUtil.directionCheck(loc, player.getEyeHeight(), direction, dLoc.getX(), dLoc.getY() + height / 2D, dLoc.getZ(), width, height, TrigUtil.DIRECTION_PRECISION);
// Also take into account the angle.
off = TrigUtil.directionCheck(loc, player.getEyeHeight(), direction, dLoc.getX(), dLoc.getY() + height / 2D, dLoc.getZ(), width, height, TrigUtil.DIRECTION_PRECISION);
}
if (off > 0.1) {
@ -74,17 +74,17 @@ public class Direction extends Check {
cancel = executeActions(player, data.directionVL, distance, cc.directionActions);
if (cancel) {
// Deal an attack penalty time.
data.attackPenalty.applyPenalty(cc.directionPenalty);
// Deal an attack penalty time.
data.attackPenalty.applyPenalty(cc.directionPenalty);
}
} else {
// Reward the player by lowering their violation level.
// Reward the player by lowering their violation level.
data.directionVL *= 0.8D;
}
return cancel;
}
/**
* Data context for iterating over TraceEntry instances.
* @param player
@ -95,50 +95,50 @@ public class Direction extends Check {
* @param cc
* @return
*/
public DirectionContext getContext(final Player player, final Location loc, final Entity damaged, final Location damagedLoc, final FightData data, final FightConfig cc) {
final DirectionContext context = new DirectionContext();
context.damagedComplex = mcAccess.isComplexPart(damaged);
// Find out how wide the entity is.
public DirectionContext getContext(final Player player, final Location loc, final Entity damaged, final Location damagedLoc, final FightData data, final FightConfig cc) {
final DirectionContext context = new DirectionContext();
context.damagedComplex = mcAccess.isComplexPart(damaged);
// Find out how wide the entity is.
context.damagedWidth = mcAccess.getWidth(damaged);
// entity.height is broken and will always be 0, therefore. Calculate height instead based on boundingBox.
context.damagedHeight = mcAccess.getHeight(damaged);
context.direction = loc.getDirection();
context.lengthDirection = context.direction.length();
return context;
}
/**
* Check if the player fails the direction check, no change of FightData.
* @param player
* @param loc
* @param damaged
* @param dLoc
* @param context
* @param data
* @param cc
* @return
*/
public boolean loopCheck(final Player player, final Location loc, final Entity damaged, final TraceEntry dLoc, final DirectionContext context, final FightData data, final FightConfig cc) {
// Ignore complex entities for the moment.
if (context.damagedComplex) {
// TODO: Revise :p
return false;
}
boolean cancel = false;
return context;
}
/**
* Check if the player fails the direction check, no change of FightData.
* @param player
* @param loc
* @param damaged
* @param dLoc
* @param context
* @param data
* @param cc
* @return
*/
public boolean loopCheck(final Player player, final Location loc, final Entity damaged, final TraceEntry dLoc, final DirectionContext context, final FightData data, final FightConfig cc) {
// Ignore complex entities for the moment.
if (context.damagedComplex) {
// TODO: Revise :p
return false;
}
boolean cancel = false;
// TODO: allow any hit on the y axis (might just adapt interface to use foot position + height)!
// How far "off" is the player with their aim. We calculate from the players eye location and view direction to
// the center of the target entity. If the line of sight is more too far off, "off" will be bigger than 0.
final double off;
if (cc.directionStrict){
off = TrigUtil.combinedDirectionCheck(loc, player.getEyeHeight(), context.direction, dLoc.x, dLoc.y + context.damagedHeight / 2D, dLoc.z, context.damagedWidth, context.damagedHeight, TrigUtil.DIRECTION_PRECISION, 80.0);
off = TrigUtil.combinedDirectionCheck(loc, player.getEyeHeight(), context.direction, dLoc.x, dLoc.y + context.damagedHeight / 2D, dLoc.z, context.damagedWidth, context.damagedHeight, TrigUtil.DIRECTION_PRECISION, 80.0);
}
else{
// Also take into account the angle.
off = TrigUtil.directionCheck(loc, player.getEyeHeight(), context.direction, dLoc.x, dLoc.y + context.damagedHeight / 2D, dLoc.z, context.damagedWidth, context.damagedHeight, TrigUtil.DIRECTION_PRECISION);
// Also take into account the angle.
off = TrigUtil.directionCheck(loc, player.getEyeHeight(), context.direction, dLoc.x, dLoc.y + context.damagedHeight / 2D, dLoc.z, context.damagedWidth, context.damagedHeight, TrigUtil.DIRECTION_PRECISION);
}
if (off > 0.1) {
@ -148,28 +148,28 @@ public class Direction extends Check {
context.minViolation = Math.min(context.minViolation, distance);
}
context.minResult = Math.min(context.minResult, off);
return cancel;
}
/**
* Apply changes to FightData according to check results (context), trigger violations.
* @param player
* @param loc
* @param damaged
* @param context
* @param forceViolation
* @param data
* @param cc
* @return
*/
public boolean loopFinish(final Player player, final Location loc, final Entity damaged, final DirectionContext context, final boolean forceViolation, final FightData data, final FightConfig cc) {
boolean cancel = false;
final double off = forceViolation && context.minViolation != Double.MAX_VALUE ? context.minViolation : context.minResult;
if (off == Double.MAX_VALUE) {
return false;
}
else if (off > 0.1) {
}
/**
* Apply changes to FightData according to check results (context), trigger violations.
* @param player
* @param loc
* @param damaged
* @param context
* @param forceViolation
* @param data
* @param cc
* @return
*/
public boolean loopFinish(final Player player, final Location loc, final Entity damaged, final DirectionContext context, final boolean forceViolation, final FightData data, final FightConfig cc) {
boolean cancel = false;
final double off = forceViolation && context.minViolation != Double.MAX_VALUE ? context.minViolation : context.minResult;
if (off == Double.MAX_VALUE) {
return false;
}
else if (off > 0.1) {
// Add the overall violation level of the check.
data.directionVL += context.minViolation;
@ -178,16 +178,16 @@ public class Direction extends Check {
cancel = executeActions(player, data.directionVL, context.minViolation, cc.directionActions);
if (cancel) {
// Deal an attack penalty time.
data.attackPenalty.applyPenalty(cc.directionPenalty);
// Deal an attack penalty time.
data.attackPenalty.applyPenalty(cc.directionPenalty);
}
}
else {
// Reward the player by lowering their violation level.
else {
// Reward the player by lowering their violation level.
data.directionVL *= 0.8D;
}
return cancel;
}
return cancel;
}
}

View File

@ -8,16 +8,16 @@ import org.bukkit.util.Vector;
*
*/
public class DirectionContext {
public boolean damagedComplex;
public double damagedWidth;
public double damagedHeight;
public Vector direction = null;
public double lengthDirection;
/** Minimum value for the distance that was a violation. */
public double minViolation = Double.MAX_VALUE;
/** Minimum value for off. */
public double minResult = Double.MAX_VALUE;
public boolean damagedComplex;
public double damagedWidth;
public double damagedHeight;
public Vector direction = null;
public double lengthDirection;
/** Minimum value for the distance that was a violation. */
public double minViolation = Double.MAX_VALUE;
/** Minimum value for off. */
public double minResult = Double.MAX_VALUE;
}

View File

@ -21,13 +21,13 @@ import fr.neatmonster.nocheatplus.permissions.Permissions;
*/
public class FightConfig extends ACheckConfig {
/** The factory creating configurations. */
public static final CheckConfigFactory factory = new CheckConfigFactory() {
@Override
public final ICheckConfig getConfig(final Player player) {
return FightConfig.getConfig(player);
}
};
/** The factory creating configurations. */
public static final CheckConfigFactory factory = new CheckConfigFactory() {
@Override
public final ICheckConfig getConfig(final Player player) {
return FightConfig.getConfig(player);
}
};
/** The map containing the configurations per world. */
private static final Map<String, FightConfig> worldsMap = new HashMap<String, FightConfig>();
@ -56,7 +56,7 @@ public class FightConfig extends ACheckConfig {
public final boolean angleCheck;
public final int angleThreshold;
public final ActionList angleActions;
public final long toolChangeAttackPenalty;
public final boolean criticalCheck;
@ -65,18 +65,18 @@ public class FightConfig extends ACheckConfig {
public final ActionList criticalActions;
public final boolean directionCheck;
public final boolean directionStrict;
public final boolean directionStrict;
public final long directionPenalty;
public final ActionList directionActions;
public final boolean fastHealCheck;
public final long fastHealInterval;
public final long fastHealBuffer;
public final ActionList fastHealActions;
public final boolean godModeCheck;
public final long godModeLagMinAge;
public final long godModeLagMaxAge;
public final long godModeLagMinAge;
public final long godModeLagMaxAge;
public final ActionList godModeActions;
public final boolean knockbackCheck;
@ -89,30 +89,30 @@ public class FightConfig extends ACheckConfig {
public final boolean reachCheck;
public final long reachPenalty;
public final boolean reachPrecision;
public final boolean reachReduce;
public final double reachSurvivalDistance;
public final double reachReduceDistance;
public final double reachReduceStep;
public final boolean reachReduce;
public final double reachSurvivalDistance;
public final double reachReduceDistance;
public final double reachReduceStep;
public final ActionList reachActions;
public final boolean selfHitCheck;
public final ActionList selfHitActions;
public final ActionList selfHitActions;
public final boolean speedCheck;
public final int speedLimit;
public final int speedBuckets;
public final long speedBucketDur;
public final float speedBucketFactor;
public final int speedBuckets;
public final long speedBucketDur;
public final float speedBucketFactor;
public final int speedShortTermLimit;
public final int speedShortTermTicks;
public final int speedShortTermTicks;
public final ActionList speedActions;
// Special flags:
public final boolean yawRateCheck;
public final boolean cancelDead;
public final boolean yawRateCheck;
public final boolean cancelDead;
/**
* Instantiates a new fight configuration.
*
@ -124,7 +124,7 @@ public class FightConfig extends ACheckConfig {
angleCheck = data.getBoolean(ConfPaths.FIGHT_ANGLE_CHECK);
angleThreshold = data.getInt(ConfPaths.FIGHT_ANGLE_THRESHOLD);
angleActions = data.getOptimizedActionList(ConfPaths.FIGHT_ANGLE_ACTIONS, Permissions.FIGHT_ANGLE);
toolChangeAttackPenalty = data.getLong(ConfPaths.FIGHT_TOOLCHANGEPENALTY);
criticalCheck = data.getBoolean(ConfPaths.FIGHT_CRITICAL_CHECK);
@ -136,7 +136,7 @@ public class FightConfig extends ACheckConfig {
directionStrict = data.getBoolean(ConfPaths.FIGHT_DIRECTION_STRICT);
directionPenalty = data.getLong(ConfPaths.FIGHT_DIRECTION_PENALTY);
directionActions = data.getOptimizedActionList(ConfPaths.FIGHT_DIRECTION_ACTIONS, Permissions.FIGHT_DIRECTION);
fastHealCheck = data.getBoolean(ConfPaths.FIGHT_FASTHEAL_CHECK);;
fastHealInterval = data.getLong(ConfPaths.FIGHT_FASTHEAL_INTERVAL);
fastHealBuffer = data.getLong(ConfPaths.FIGHT_FASTHEAL_BUFFER);
@ -165,7 +165,7 @@ public class FightConfig extends ACheckConfig {
selfHitCheck = data.getBoolean(ConfPaths.FIGHT_SELFHIT_CHECK);
selfHitActions = data.getOptimizedActionList(ConfPaths.FIGHT_SELFHIT_ACTIONS, Permissions.FIGHT_SELFHIT);
speedCheck = data.getBoolean(ConfPaths.FIGHT_SPEED_CHECK);
speedLimit = data.getInt(ConfPaths.FIGHT_SPEED_LIMIT);
speedBuckets = data.getInt(ConfPaths.FIGHT_SPEED_BUCKETS_N, 6);
@ -174,8 +174,8 @@ public class FightConfig extends ACheckConfig {
speedShortTermLimit = data.getInt(ConfPaths.FIGHT_SPEED_SHORTTERM_LIMIT);
speedShortTermTicks = data.getInt(ConfPaths.FIGHT_SPEED_SHORTTERM_TICKS);
speedActions = data.getOptimizedActionList(ConfPaths.FIGHT_SPEED_ACTIONS, Permissions.FIGHT_SPEED);
yawRateCheck = data.getBoolean(ConfPaths.FIGHT_YAWRATE_CHECK, true);
cancelDead = data.getBoolean(ConfPaths.FIGHT_CANCELDEAD);
}
@ -203,9 +203,9 @@ public class FightConfig extends ACheckConfig {
case FIGHT_SPEED:
return speedCheck;
case FIGHT_SELFHIT:
return selfHitCheck;
return selfHitCheck;
case FIGHT_FASTHEAL:
return fastHealCheck;
return fastHealCheck;
default:
return true;
}

View File

@ -21,77 +21,77 @@ import fr.neatmonster.nocheatplus.utilities.PenaltyTime;
* Player specific data for the fight checks.
*/
public class FightData extends ACheckData {
public static class FightDataFactory implements CheckDataFactory {
protected FightDataFactory() {
// Discourage creation here.
};
@Override
public final ICheckData getData(final Player player) {
return FightData.getData(player);
}
@Override
public ICheckData removeData(final String playerName) {
return FightData.removeData(playerName);
}
public static class FightDataFactory implements CheckDataFactory {
@Override
public void removeAllData() {
clear();
}
}
protected FightDataFactory() {
// Discourage creation here.
};
@Override
public final ICheckData getData(final Player player) {
return FightData.getData(player);
}
@Override
public ICheckData removeData(final String playerName) {
return FightData.removeData(playerName);
}
@Override
public void removeAllData() {
clear();
}
}
/** The factory for general fight data. */
public static final CheckDataFactory factory = new FightDataFactory();
/** SelfHit factory */
public static final CheckDataFactory selfHitDataFactory = new SubCheckDataFactory<FightData>(CheckType.FIGHT, factory) {
/** The factory for general fight data. */
public static final CheckDataFactory factory = new FightDataFactory();
@Override
protected FightData getData(String playerName) {
return playersMap.get(playerName);
}
/** SelfHit factory */
public static final CheckDataFactory selfHitDataFactory = new SubCheckDataFactory<FightData>(CheckType.FIGHT, factory) {
@Override
protected Collection<String> getPresentData() {
return playersMap.keySet();
}
@Override
protected FightData getData(String playerName) {
return playersMap.get(playerName);
}
@Override
protected boolean hasData(String playerName) {
return playersMap.containsKey(playerName);
}
@Override
protected Collection<String> getPresentData() {
return playersMap.keySet();
}
@Override
protected boolean removeFromData(String playerName, FightData data) {
if (data.selfHitVL.score(1f) > 0f) {
data.selfHitVL.clear(System.currentTimeMillis());
return true;
}
else {
return false;
}
}
};
public static CheckDataFactory getCheckDataFactory(CheckType checkType) {
if (checkType != CheckType.FIGHT && !APIUtils.isParent(CheckType.FIGHT, checkType)) {
throw new IllegalArgumentException("Can only return a CheckDataFactory for the check group FIGHT.");
}
switch(checkType) {
// Note that CheckType does need adaption for new entries (!).
case FIGHT_SELFHIT:
return selfHitDataFactory;
default:
return factory;
}
}
@Override
protected boolean hasData(String playerName) {
return playersMap.containsKey(playerName);
}
@Override
protected boolean removeFromData(String playerName, FightData data) {
if (data.selfHitVL.score(1f) > 0f) {
data.selfHitVL.clear(System.currentTimeMillis());
return true;
}
else {
return false;
}
}
};
public static CheckDataFactory getCheckDataFactory(CheckType checkType) {
if (checkType != CheckType.FIGHT && !APIUtils.isParent(CheckType.FIGHT, checkType)) {
throw new IllegalArgumentException("Can only return a CheckDataFactory for the check group FIGHT.");
}
switch(checkType) {
// Note that CheckType does need adaption for new entries (!).
case FIGHT_SELFHIT:
return selfHitDataFactory;
default:
return factory;
}
}
/** The map containing the data per players. */
protected static final Map<String, FightData> playersMap = new HashMap<String, FightData>(); // Not sure about visibility (selfhit).
@ -110,14 +110,14 @@ public class FightData extends ACheckData {
}
public static ICheckData removeData(final String playerName) {
return playersMap.remove(playerName);
}
public static void clear(){
playersMap.clear();
return playersMap.remove(playerName);
}
// Violation levels.
public static void clear(){
playersMap.clear();
}
// Violation levels.
public double angleVL;
public double criticalVL;
public double directionVL;
@ -127,28 +127,28 @@ public class FightData extends ACheckData {
public double noSwingVL;
public double reachVL;
public double speedVL;
// Shared
public String lastWorld = "";
public int lastAttackTick = 0;
public double lastAttackedX = Double.MAX_VALUE;
public double lastAttackedY;
public double lastAttackedZ;
/** Attack penalty (close combat, ENTITY_ATTACK). */
public final PenaltyTime attackPenalty = new PenaltyTime();
/** The entity id which might get counter-attacked. */
public int thornsId = Integer.MIN_VALUE;
/** Any kind of health regeneration. */
public long regainHealthTime = 0;
// public double lastAttackedDist = 0.0;
public long regainHealthTime = 0;
// public double lastAttackedDist = 0.0;
public long damageTakenByEntityTick;
// Data of the angle check.
public TreeMap<Long, Location> angleHits = new TreeMap<Long, Location>();
// FastHeal
public long fastHealRefTime = 0;
/** Buffer has to be initialized in constructor. */
@ -158,14 +158,14 @@ public class FightData extends ACheckData {
public int godModeBuffer;
public int godModeLastAge;
public long godModeLastTime;
// New god mode check [in progress].
public int godModeHealthDecreaseTick = 0;
public double godModeHealth = 0.0;
public int godModeHealthDecreaseTick = 0;
public double godModeHealth = 0.0;
public int lastDamageTick = 0;
public int lastNoDamageTicks = 0;
/** Accumulator. */
public int godModeAcc = 0;
public int godModeAcc = 0;
// Data of the knockback check.
public long knockbackSprintTime;
@ -174,35 +174,35 @@ public class FightData extends ACheckData {
public boolean noSwingArmSwung;
// Data of the reach check.
public double reachMod = 1;
public double reachMod = 1;
// Data of the SelfHit check.
public ActionFrequency selfHitVL = new ActionFrequency(6, 5000);
// Data of the frequency check.
public final ActionFrequency speedBuckets;
public int speedShortTermCount;
public int speedShortTermTick;
// TNT workaround: Allow ENTITY_ATTACK if these attributes match.
// Discussion at: https://github.com/NoCheatPlus/NoCheatPlus/pull/17 (@Iceee)
/** Tick the last explosion damage was dealt at. */
public int lastExplosionDamageTick = -1 ;
/** Last explosion damaged entity (id). */
public int lastExplosionEntityId = Integer.MAX_VALUE;
public FightData(final FightConfig cc){
speedBuckets = new ActionFrequency(cc.speedBuckets, cc.speedBucketDur);
// Start with full fast-heal buffer.
fastHealBuffer = cc.fastHealBuffer;
}
public int speedShortTermCount;
public int speedShortTermTick;
// TNT workaround: Allow ENTITY_ATTACK if these attributes match.
// Discussion at: https://github.com/NoCheatPlus/NoCheatPlus/pull/17 (@Iceee)
/** Tick the last explosion damage was dealt at. */
public int lastExplosionDamageTick = -1 ;
/** Last explosion damaged entity (id). */
public int lastExplosionEntityId = Integer.MAX_VALUE;
public FightData(final FightConfig cc){
speedBuckets = new ActionFrequency(cc.speedBuckets, cc.speedBucketDur);
// Start with full fast-heal buffer.
fastHealBuffer = cc.fastHealBuffer;
}
public void onWorldChange() {
angleHits.clear();
lastAttackedX = Double.MAX_VALUE;
lastAttackTick = 0;
lastWorld = "";
}
public void onWorldChange() {
angleHits.clear();
lastAttackedX = Double.MAX_VALUE;
lastAttackTick = 0;
lastWorld = "";
}
}

View File

@ -59,7 +59,7 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
/** The direction check. */
private final Direction direction = addCheck(new Direction());
/** Faster health regeneration check. */
private final FastHeal fastHeal = addCheck(new FastHeal());
@ -74,24 +74,24 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
/** The reach check. */
private final Reach reach = addCheck(new Reach());
/** The self hit check */
private final SelfHit selfHit = addCheck(new SelfHit());
/** The speed check. */
private final Speed speed = addCheck(new Speed());
/** For temporary use: LocUtil.clone before passing deeply, call setWorld(null) after use. */
private final Location useLoc1 = new Location(null, 0, 0, 0);
/** For temporary use: LocUtil.clone before passing deeply, call setWorld(null) after use. */
private final Location useLoc2 = new Location(null, 0, 0, 0);
private final Counters counters = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class);
private final Location useLoc1 = new Location(null, 0, 0, 0);
/** For temporary use: LocUtil.clone before passing deeply, call setWorld(null) after use. */
private final Location useLoc2 = new Location(null, 0, 0, 0);
private final Counters counters = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class);
private final int idCancelDead = counters.registerKey("canceldead");
public FightListener(){
super(CheckType.FIGHT);
super(CheckType.FIGHT);
}
/**
@ -103,27 +103,27 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
*/
private boolean handleNormalDamage(final Player player, final Entity damaged, final double damage, final int tick, final FightData data) {
final FightConfig cc = FightConfig.getConfig(player);
// Hotfix attempt for enchanted books.
// TODO: maybe a generaluzed version for the future...
final ItemStack stack = player.getItemInHand();
// Illegal enchantments hotfix check.
if (Items.checkIllegalEnchantments(player, stack)) return true;
boolean cancelled = false;
final String worldName = player.getWorld().getName();
final long now = System.currentTimeMillis();
final boolean worldChanged = !worldName.equals(data.lastWorld);
final Location loc = player.getLocation(useLoc1);
// // Bad pitch/yaw, just in case.
// if (LocUtil.needsDirectionCorrection(useLoc1.getYaw(), useLoc1.getPitch())) {
// mcAccess.correctDirection(player);
// player.getLocation(useLoc1);
// }
// // Bad pitch/yaw, just in case.
// if (LocUtil.needsDirectionCorrection(useLoc1.getYaw(), useLoc1.getPitch())) {
// mcAccess.correctDirection(player);
// player.getLocation(useLoc1);
// }
final Location damagedLoc = damaged.getLocation(useLoc2);
// final double targetDist = CheckUtils.distance(loc, targetLoc); // TODO: Calculate distance as is done in fight.reach !
// final double targetDist = CheckUtils.distance(loc, targetLoc); // TODO: Calculate distance as is done in fight.reach !
final double targetMove;
final int tickAge;
final long msAge; // Milliseconds the ticks actually took.
@ -131,267 +131,267 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
// TODO: relative distance (player - target)!
// TODO: Use trace for this ?
if (data.lastAttackedX == Double.MAX_VALUE || tick < data.lastAttackTick || worldChanged || tick - data.lastAttackTick > 20){
// TODO: 20 ?
tickAge = 0;
targetMove = 0.0;
normalizedMove = 0.0;
msAge = 0;
// TODO: 20 ?
tickAge = 0;
targetMove = 0.0;
normalizedMove = 0.0;
msAge = 0;
}
else{
tickAge = tick - data.lastAttackTick;
// TODO: Maybe use 3d distance if dy(normalized) is too big.
targetMove = TrigUtil.distance(data.lastAttackedX, data.lastAttackedZ, damagedLoc.getX(), damagedLoc.getZ());
msAge = (long) (50f * TickTask.getLag(50L * tickAge, true) * (float) tickAge);
normalizedMove = msAge == 0 ? targetMove : targetMove * Math.min(20.0, 1000.0 / (double) msAge);
tickAge = tick - data.lastAttackTick;
// TODO: Maybe use 3d distance if dy(normalized) is too big.
targetMove = TrigUtil.distance(data.lastAttackedX, data.lastAttackedZ, damagedLoc.getX(), damagedLoc.getZ());
msAge = (long) (50f * TickTask.getLag(50L * tickAge, true) * (float) tickAge);
normalizedMove = msAge == 0 ? targetMove : targetMove * Math.min(20.0, 1000.0 / (double) msAge);
}
// TODO: calculate factor for dists: ticks * 50 * lag
// TODO: dist < width => skip some checks (direction, ..)
final LocationTrace damagedTrace;
final Player damagedPlayer;
if (damaged instanceof Player){
damagedPlayer = (Player) damaged;
// // Bad pitch/yaw, just in case.
// if (LocUtil.needsDirectionCorrection(useLoc2.getYaw(), useLoc2.getPitch())) {
// mcAccess.correctDirection(damagedPlayer);
// damagedPlayer.getLocation(useLoc2);
// }
// Log.
if (cc.debug && damagedPlayer.hasPermission(Permissions.ADMINISTRATION_DEBUG)){
damagedPlayer.sendMessage("Attacked by " + player.getName() + ": inv=" + mcAccess.getInvulnerableTicks(damagedPlayer) + " ndt=" + damagedPlayer.getNoDamageTicks());
}
// Check for self hit exploits (mind that projectiles are excluded from this.)
if (selfHit.isEnabled(player) && selfHit.check(player, damagedPlayer, data, cc)) {
cancelled = true;
}
// Get+update the damaged players.
// TODO: Problem with NPCs: data stays (not a big problem).
// (This is done even if the event has already been cancelled, to keep track, if the player is on a horse.)
damagedTrace = MovingData.getData(damagedPlayer).updateTrace(damagedPlayer, damagedLoc, tick);
damagedPlayer = (Player) damaged;
// // Bad pitch/yaw, just in case.
// if (LocUtil.needsDirectionCorrection(useLoc2.getYaw(), useLoc2.getPitch())) {
// mcAccess.correctDirection(damagedPlayer);
// damagedPlayer.getLocation(useLoc2);
// }
// Log.
if (cc.debug && damagedPlayer.hasPermission(Permissions.ADMINISTRATION_DEBUG)){
damagedPlayer.sendMessage("Attacked by " + player.getName() + ": inv=" + mcAccess.getInvulnerableTicks(damagedPlayer) + " ndt=" + damagedPlayer.getNoDamageTicks());
}
// Check for self hit exploits (mind that projectiles are excluded from this.)
if (selfHit.isEnabled(player) && selfHit.check(player, damagedPlayer, data, cc)) {
cancelled = true;
}
// Get+update the damaged players.
// TODO: Problem with NPCs: data stays (not a big problem).
// (This is done even if the event has already been cancelled, to keep track, if the player is on a horse.)
damagedTrace = MovingData.getData(damagedPlayer).updateTrace(damagedPlayer, damagedLoc, tick);
} else {
damagedPlayer = null; // TODO: This is a temporary workaround.
// Use a fake trace.
// TODO: Provide for entities too? E.g. one per player, or a fully fledged bookkeeping thing (EntityData).
//final MovingConfig mcc = MovingConfig.getConfig(damagedLoc.getWorld().getName());
damagedTrace = null; //new LocationTrace(mcc.traceSize, mcc.traceMergeDist);
//damagedTrace.addEntry(tick, damagedLoc.getX(), damagedLoc.getY(), damagedLoc.getZ());
damagedPlayer = null; // TODO: This is a temporary workaround.
// Use a fake trace.
// TODO: Provide for entities too? E.g. one per player, or a fully fledged bookkeeping thing (EntityData).
//final MovingConfig mcc = MovingConfig.getConfig(damagedLoc.getWorld().getName());
damagedTrace = null; //new LocationTrace(mcc.traceSize, mcc.traceMergeDist);
//damagedTrace.addEntry(tick, damagedLoc.getX(), damagedLoc.getY(), damagedLoc.getZ());
}
if (cc.cancelDead){
if (damaged.isDead()) {
cancelled = true;
}
// Only allow damaging others if taken damage this tick.
if (damaged.isDead()) {
cancelled = true;
}
// Only allow damaging others if taken damage this tick.
if (player.isDead() && data.damageTakenByEntityTick != TickTask.getTick()){
cancelled = true;
cancelled = true;
}
}
if (damage <= 4.0 && tick == data.damageTakenByEntityTick && data.thornsId != Integer.MIN_VALUE && data.thornsId == damaged.getEntityId()){
// Don't handle further, but do respect selfhit/canceldead.
// TODO: Remove soon.
data.thornsId = Integer.MIN_VALUE;
return cancelled;
// Don't handle further, but do respect selfhit/canceldead.
// TODO: Remove soon.
data.thornsId = Integer.MIN_VALUE;
return cancelled;
}
else {
data.thornsId = Integer.MIN_VALUE;
data.thornsId = Integer.MIN_VALUE;
}
// Run through the main checks.
if (!cancelled && speed.isEnabled(player)){
if (speed.check(player, now)){
cancelled = true;
// Still feed the improbable.
if (data.speedVL > 50){
Improbable.check(player, 2f, now, "fight.speed");
}
else{
Improbable.feed(player, 2f, now);
}
}
else if (normalizedMove > 2.0 && Improbable.check(player, 1f, now, "fight.speed")){
// Feed improbable in case of ok-moves too.
// TODO: consider only feeding if attacking with higher average speed (!)
cancelled = true;
}
if (speed.check(player, now)){
cancelled = true;
// Still feed the improbable.
if (data.speedVL > 50){
Improbable.check(player, 2f, now, "fight.speed");
}
else{
Improbable.feed(player, 2f, now);
}
}
else if (normalizedMove > 2.0 && Improbable.check(player, 1f, now, "fight.speed")){
// Feed improbable in case of ok-moves too.
// TODO: consider only feeding if attacking with higher average speed (!)
cancelled = true;
}
}
if (!cancelled && critical.isEnabled(player) && critical.check(player, loc, data, cc)) {
cancelled = true;
cancelled = true;
}
if (!cancelled && knockback.isEnabled(player) && knockback.check(player, data, cc)) {
cancelled = true;
cancelled = true;
}
if (!cancelled && noSwing.isEnabled(player) && noSwing.check(player, data, cc)) {
cancelled = true;
cancelled = true;
}
if (!cancelled && player.isBlocking() && !player.hasPermission(Permissions.MOVING_SURVIVALFLY_BLOCKING)) {
cancelled = true;
cancelled = true;
}
// TODO: Order of all these checks ...
// Checks that use LocationTrace.
// TODO: Later optimize (...), should reverse check window ?
// First loop through reach and direction, to determine a window.
final boolean reachEnabled = !cancelled && reach.isEnabled(player);
final boolean directionEnabled = !cancelled && direction.isEnabled(player);
if (reachEnabled || directionEnabled) {
if (damagedPlayer != null) {
// TODO: Move to a method (trigonometric checks).
if (damagedPlayer != null) {
// TODO: Move to a method (trigonometric checks).
final ReachContext reachContext = reachEnabled ? reach.getContext(player, loc, damaged, damagedLoc, data, cc) : null;
final DirectionContext directionContext = directionEnabled ? direction.getContext(player, loc, damaged, damagedLoc, data, cc) : null;
final long traceOldest = tick; // - damagedTrace.getMaxSize(); // TODO: Set by window.
// TODO: Iterating direction: could also start from latest, be it on occasion.
Iterator<TraceEntry> traceIt = damagedTrace.maxAgeIterator(traceOldest);
final Iterator<TraceEntry> traceIt = damagedTrace.maxAgeIterator(traceOldest);
boolean violation = true; // No tick with all checks passed.
boolean reachPassed = !reachEnabled; // Passed individually for some tick.
boolean directionPassed = !directionEnabled; // Passed individually for some tick.
// TODO: Maintain a latency estimate + max diff and invalidate completely (i.e. iterate from latest NEXT time)], or just max latency.
while (traceIt.hasNext()) {
final TraceEntry entry = traceIt.next();
// Simplistic just check both until end or hit.
// TODO: Other default distances/tolerances.
boolean thisPassed = true;
if (reachEnabled) {
if (reach.loopCheck(player, loc, damagedPlayer, entry, reachContext, data, cc)) {
thisPassed = false;
} else {
reachPassed = true;
}
}
// TODO: For efficiency one could omit checking at all if reach is failed all the time.
if (directionEnabled && (reachPassed || !directionPassed)) {
if (direction.loopCheck(player, damagedLoc, damagedPlayer, entry, directionContext, data, cc)) {
thisPassed = false;
} else {
directionPassed = true;
}
}
if (thisPassed) {
// TODO: Log/set estimated latency.
violation = false;
break;
}
final TraceEntry entry = traceIt.next();
// Simplistic just check both until end or hit.
// TODO: Other default distances/tolerances.
boolean thisPassed = true;
if (reachEnabled) {
if (reach.loopCheck(player, loc, damagedPlayer, entry, reachContext, data, cc)) {
thisPassed = false;
} else {
reachPassed = true;
}
}
// TODO: For efficiency one could omit checking at all if reach is failed all the time.
if (directionEnabled && (reachPassed || !directionPassed)) {
if (direction.loopCheck(player, damagedLoc, damagedPlayer, entry, directionContext, data, cc)) {
thisPassed = false;
} else {
directionPassed = true;
}
}
if (thisPassed) {
// TODO: Log/set estimated latency.
violation = false;
break;
}
}
// TODO: How to treat mixed state: violation && reachPassed && directionPassed [current: use min violation // thinkable: silent cancel, if actions have cancel (!)]
// TODO: Adapt according to strictness settings?
if (reachEnabled) {
// TODO: Might ignore if already cancelled by mixed/silent cancel.
if (reach.loopFinish(player, loc, damagedPlayer, reachContext, violation, data, cc)) {
cancelled = true;
}
// TODO: Might ignore if already cancelled by mixed/silent cancel.
if (reach.loopFinish(player, loc, damagedPlayer, reachContext, violation, data, cc)) {
cancelled = true;
}
}
if (directionEnabled) {
// TODO: Might ignore if already cancelled.
if (direction.loopFinish(player, loc, damagedPlayer, directionContext, violation, data, cc)) {
cancelled = true;
}
// TODO: Might ignore if already cancelled.
if (direction.loopFinish(player, loc, damagedPlayer, directionContext, violation, data, cc)) {
cancelled = true;
}
}
// TODO: Log exact state, probably record min/max latency (individually).
} else {
// Still use the classic methods for non-players. maybe[]
if (reachEnabled && reach.check(player, loc, damaged, damagedLoc, data, cc)) {
cancelled = true;
} else {
// Still use the classic methods for non-players. maybe[]
if (reachEnabled && reach.check(player, loc, damaged, damagedLoc, data, cc)) {
cancelled = true;
}
if (directionEnabled && direction.check(player, loc, damaged, damagedLoc, data, cc)) {
cancelled = true;
cancelled = true;
}
}
}
}
// Check angle with allowed window.
if (angle.isEnabled(player)) {
// TODO: Revise, use own trace.
// The "fast turning" checks are checked in any case because they accumulate data.
// Improbable yaw changing: Moving events might be missing up to a ten degrees change.
if (Combined.checkYawRate(player, loc.getYaw(), now, worldName, cc.yawRateCheck)) {
// (Check or just feed).
// TODO: Work into this somehow attacking the same aim and/or similar aim position (not cancel then).
cancelled = true;
}
// Angle check.
if (angle.check(player, worldChanged, data, cc)) {
if (!cancelled && cc.debug) {
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(Streams.TRACE_FILE, player.getName() + " fight.angle cancel without yawrate cancel.");
}
cancelled = true;
}
}
// TODO: Revise, use own trace.
// The "fast turning" checks are checked in any case because they accumulate data.
// Improbable yaw changing: Moving events might be missing up to a ten degrees change.
if (Combined.checkYawRate(player, loc.getYaw(), now, worldName, cc.yawRateCheck)) {
// (Check or just feed).
// TODO: Work into this somehow attacking the same aim and/or similar aim position (not cancel then).
cancelled = true;
}
// Angle check.
if (angle.check(player, worldChanged, data, cc)) {
if (!cancelled && cc.debug) {
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(Streams.TRACE_FILE, player.getName() + " fight.angle cancel without yawrate cancel.");
}
cancelled = true;
}
}
// Set values.
data.lastWorld = worldName;
data.lastAttackTick = tick;
data.lastAttackedX = damagedLoc.getX();
data.lastAttackedY = damagedLoc.getY();
data.lastAttackedZ = damagedLoc.getZ();
// data.lastAttackedDist = targetDist;
// Care for the "lost sprint problem": sprint resets, client moves as if still...
// TODO: Use stored distance calculation same as reach check?
// TODO: For pvp: make use of "player was there" heuristic later on.
// TODO: Confine further with simple pre-conditions.
// TODO: Evaluate if moving traces can help here.
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){
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.
final MovingConfig mc = MovingConfig.getConfig(player);
if (now <= mData.timeSprinting + mc.sprintingGrace && MovingListener.shouldCheckSurvivalFly(player, mData, mc)){
// Judge as "lost sprint" problem.
// TODO: What would mData.lostSprintCount > 0 mean here?
mData.lostSprintCount = 7;
if ((cc.debug || mc.debug) && BuildParameters.debugLevel > 0){
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(Streams.TRACE_FILE, player.getName() + " (lostsprint) hDist to last from: " + hDist + " | targetdist=" + TrigUtil.distance(loc.getX(), loc.getZ(), damagedLoc.getX(), damagedLoc.getZ()) + " | sprinting=" + player.isSprinting() + " | food=" + player.getFoodLevel() +" | hbuf=" + mData.sfHorizontalBuffer);
}
}
}
}
}
// Generic attacking penalty.
// (Cancel after sprinting hacks, because of potential fp).
if (!cancelled && data.attackPenalty.isPenalty(now)) {
cancelled = true;
if (cc.debug) {
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(Streams.TRACE_FILE, player.getName() + " ~ attack penalty.");
}
data.lastAttackTick = tick;
data.lastAttackedX = damagedLoc.getX();
data.lastAttackedY = damagedLoc.getY();
data.lastAttackedZ = damagedLoc.getZ();
// data.lastAttackedDist = targetDist;
// Care for the "lost sprint problem": sprint resets, client moves as if still...
// TODO: Use stored distance calculation same as reach check?
// TODO: For pvp: make use of "player was there" heuristic later on.
// TODO: Confine further with simple pre-conditions.
// TODO: Evaluate if moving traces can help here.
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){
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.
final MovingConfig mc = MovingConfig.getConfig(player);
if (now <= mData.timeSprinting + mc.sprintingGrace && MovingListener.shouldCheckSurvivalFly(player, mData, mc)){
// Judge as "lost sprint" problem.
// TODO: What would mData.lostSprintCount > 0 mean here?
mData.lostSprintCount = 7;
if ((cc.debug || mc.debug) && BuildParameters.debugLevel > 0){
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(Streams.TRACE_FILE, player.getName() + " (lostsprint) hDist to last from: " + hDist + " | targetdist=" + TrigUtil.distance(loc.getX(), loc.getZ(), damagedLoc.getX(), damagedLoc.getZ()) + " | sprinting=" + player.isSprinting() + " | food=" + player.getFoodLevel() +" | hbuf=" + mData.sfHorizontalBuffer);
}
}
}
}
}
// Cleanup.
// Generic attacking penalty.
// (Cancel after sprinting hacks, because of potential fp).
if (!cancelled && data.attackPenalty.isPenalty(now)) {
cancelled = true;
if (cc.debug) {
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(Streams.TRACE_FILE, player.getName() + " ~ attack penalty.");
}
}
// Cleanup.
useLoc1.setWorld(null);
useLoc2.setWorld(null);
return cancelled;
}
/**
* Check if a player might return some damage due to the "thorns" enchantment.
* @param player
* @return
*/
public static final boolean hasThorns(final Player player){
final PlayerInventory inv = player.getInventory();
final ItemStack[] contents = inv.getArmorContents();
for (int i = 0; i < contents.length; i++){
final ItemStack stack = contents[i];
if (stack != null && stack.getEnchantmentLevel(Enchantment.THORNS) > 0){
return true;
}
}
return false;
final PlayerInventory inv = player.getInventory();
final ItemStack[] contents = inv.getArmorContents();
for (int i = 0; i < contents.length; i++){
final ItemStack stack = contents[i];
if (stack != null && stack.getEnchantmentLevel(Enchantment.THORNS) > 0){
return true;
}
}
return false;
}
/**
@ -402,89 +402,89 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
*/
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onEntityDamage(final EntityDamageEvent event) {
final Entity damaged = event.getEntity();
final Player damagedPlayer = damaged instanceof Player ? (Player) damaged : null;
final FightData damagedData = damagedPlayer == null ? null : FightData.getData(damagedPlayer);
final boolean damagedIsDead = damaged.isDead();
if (damagedPlayer != null && !damagedIsDead) {
final Entity damaged = event.getEntity();
final Player damagedPlayer = damaged instanceof Player ? (Player) damaged : null;
final FightData damagedData = damagedPlayer == null ? null : FightData.getData(damagedPlayer);
final boolean damagedIsDead = damaged.isDead();
if (damagedPlayer != null && !damagedIsDead) {
if (!damagedPlayer.isDead() && godMode.isEnabled(damagedPlayer) && godMode.check(damagedPlayer, BridgeHealth.getDamage(event), damagedData)){
// It requested to "cancel" the players invulnerability, so set their noDamageTicks to 0.
damagedPlayer.setNoDamageTicks(0);
damagedPlayer.setNoDamageTicks(0);
}
if (BridgeHealth.getHealth(damagedPlayer) >= BridgeHealth.getMaxHealth(damagedPlayer)){
// TODO: Might use the same FightData instance for GodMode.
if (damagedData.fastHealBuffer < 0){
// Reduce negative buffer with each full health.
damagedData.fastHealBuffer /= 2;
}
// Set reference time.
damagedData.fastHealRefTime = System.currentTimeMillis();
// TODO: Might use the same FightData instance for GodMode.
if (damagedData.fastHealBuffer < 0){
// Reduce negative buffer with each full health.
damagedData.fastHealBuffer /= 2;
}
// Set reference time.
damagedData.fastHealRefTime = System.currentTimeMillis();
}
}
// NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(LogManager.TRACE_FILE, event.getCause());
// Attacking entities.
// NCPAPIProvider.getNoCheatPlusAPI().getLogManager().debug(LogManager.TRACE_FILE, event.getCause());
// Attacking entities.
if (event instanceof EntityDamageByEntityEvent) {
final EntityDamageByEntityEvent e = (EntityDamageByEntityEvent) event;
final Entity damager = e.getDamager();
final int tick = TickTask.getTick();
if (damagedPlayer != null && !damagedIsDead){
// TODO: check once more when to set this (!) in terms of order.
FightData.getData(damagedPlayer).damageTakenByEntityTick = tick;
if (damagedPlayer != null && !damagedIsDead){
// TODO: check once more when to set this (!) in terms of order.
FightData.getData(damagedPlayer).damageTakenByEntityTick = tick;
if (hasThorns(damagedPlayer)){
// TODO: Cleanup here.
// Remember the id of the attacker to allow counter damage.
damagedData.thornsId = damager.getEntityId();
}
else{
damagedData.thornsId = Integer.MIN_VALUE;
// TODO: Cleanup here.
// Remember the id of the attacker to allow counter damage.
damagedData.thornsId = damager.getEntityId();
}
}
final DamageCause damageCause = event.getCause();
final Player player = damager instanceof Player ? (Player) damager : null;
Player attacker = player;
// TODO: deobfuscate.
if (damager instanceof TNTPrimed) {
final Entity source = ((TNTPrimed) damager).getSource();
if (source instanceof Player) {
attacker = (Player) source;
}
}
if (attacker != null && (damageCause == DamageCause.BLOCK_EXPLOSION || damageCause == DamageCause.ENTITY_EXPLOSION)) {
// NOTE: Pigs don't have data.
final FightData data = FightData.getData(attacker);
data.lastExplosionEntityId = damaged.getEntityId();
data.lastExplosionDamageTick = tick;
return;
}
else{
damagedData.thornsId = Integer.MIN_VALUE;
}
}
final DamageCause damageCause = event.getCause();
final Player player = damager instanceof Player ? (Player) damager : null;
Player attacker = player;
// TODO: deobfuscate.
if (damager instanceof TNTPrimed) {
final Entity source = ((TNTPrimed) damager).getSource();
if (source instanceof Player) {
attacker = (Player) source;
}
}
if (attacker != null && (damageCause == DamageCause.BLOCK_EXPLOSION || damageCause == DamageCause.ENTITY_EXPLOSION)) {
// NOTE: Pigs don't have data.
final FightData data = FightData.getData(attacker);
data.lastExplosionEntityId = damaged.getEntityId();
data.lastExplosionDamageTick = tick;
return;
}
if (player != null){
final double damage = BridgeHealth.getDamage(e);
final FightData data = FightData.getData(player);
if (damageCause == DamageCause.ENTITY_ATTACK){
// TODO: Might/should skip the damage comparison, though checking on lowest priority.
if (damaged.getEntityId() == data.lastExplosionEntityId && tick == data.lastExplosionDamageTick) {
data.lastExplosionDamageTick = -1;
data.lastExplosionEntityId = Integer.MAX_VALUE;
} else if (handleNormalDamage(player, damaged, damage, tick, data)){
e.setCancelled(true);
}
// TODO: Might/should skip the damage comparison, though checking on lowest priority.
if (damaged.getEntityId() == data.lastExplosionEntityId && tick == data.lastExplosionDamageTick) {
data.lastExplosionDamageTick = -1;
data.lastExplosionEntityId = Integer.MAX_VALUE;
} else if (handleNormalDamage(player, damaged, damage, tick, data)){
e.setCancelled(true);
}
}
}
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onEntityDamageMonitor(final EntityDamageEvent event) {
final Entity damaged = event.getEntity();
if (damaged instanceof Player){
final Player player = (Player) damaged;
final FightData data = FightData.getData(player);
final int ndt = player.getNoDamageTicks();
if (data.lastDamageTick == TickTask.getTick() && data.lastNoDamageTicks != ndt){
// Plugin compatibility thing.
data.lastNoDamageTicks = ndt;
}
}
final Entity damaged = event.getEntity();
if (damaged instanceof Player){
final Player player = (Player) damaged;
final FightData data = FightData.getData(player);
final int ndt = player.getNoDamageTicks();
if (data.lastDamageTick == TickTask.getTick() && data.lastNoDamageTicks != ndt){
// Plugin compatibility thing.
data.lastNoDamageTicks = ndt;
}
}
}
/**
@ -500,7 +500,7 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
if (entity instanceof Player){
final Player player = (Player) entity;
if (godMode.isEnabled(player)) {
godMode.death(player);
godMode.death(player);
}
}
}
@ -526,67 +526,67 @@ public class FightListener extends CheckListener implements JoinLeaveListener{
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onPlayerToggleSprint(final PlayerToggleSprintEvent event) {
if (event.isSprinting()) {
FightData.getData(event.getPlayer()).knockbackSprintTime = System.currentTimeMillis();
FightData.getData(event.getPlayer()).knockbackSprintTime = System.currentTimeMillis();
}
}
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onEntityRegainHealthLow(final EntityRegainHealthEvent event){
final Entity entity = event.getEntity();
if (!(entity instanceof Player)) return;
final Player player = (Player) entity;
if (player.isDead() && BridgeHealth.getHealth(player) <= 0.0) {
// Heal after death.
event.setCancelled(true);
counters.addPrimaryThread(idCancelDead, 1);
return;
}
if (event.getRegainReason() != RegainReason.SATIATED) {
return;
}
if (fastHeal.isEnabled(player) && fastHeal.check(player)) {
// TODO: Can clients force events with 0-re-gain ?
event.setCancelled(true);
}
final Entity entity = event.getEntity();
if (!(entity instanceof Player)) return;
final Player player = (Player) entity;
if (player.isDead() && BridgeHealth.getHealth(player) <= 0.0) {
// Heal after death.
event.setCancelled(true);
counters.addPrimaryThread(idCancelDead, 1);
return;
}
if (event.getRegainReason() != RegainReason.SATIATED) {
return;
}
if (fastHeal.isEnabled(player) && fastHeal.check(player)) {
// TODO: Can clients force events with 0-re-gain ?
event.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onEntityRegainHealth(final EntityRegainHealthEvent event){
final Entity entity = event.getEntity();
if (!(entity instanceof Player)) return;
final Player player = (Player) entity;
final FightData data = FightData.getData(player);
// Adjust god mode data:
// Remember the time.
data.regainHealthTime = System.currentTimeMillis();
// Set god-mode health to maximum.
// TODO: Mind that health regain might half the ndt.
final double health = Math.min(BridgeHealth.getHealth(player) + BridgeHealth.getAmount(event), BridgeHealth.getMaxHealth(player));
data.godModeHealth = Math.max(data.godModeHealth, health);
final Entity entity = event.getEntity();
if (!(entity instanceof Player)) return;
final Player player = (Player) entity;
final FightData data = FightData.getData(player);
// Adjust god mode data:
// Remember the time.
data.regainHealthTime = System.currentTimeMillis();
// Set god-mode health to maximum.
// TODO: Mind that health regain might half the ndt.
final double health = Math.min(BridgeHealth.getHealth(player) + BridgeHealth.getAmount(event), BridgeHealth.getMaxHealth(player));
data.godModeHealth = Math.max(data.godModeHealth, health);
}
@Override
public void playerJoins(final Player player) {
}
@Override
public void playerJoins(final Player player) {
}
@Override
public void playerLeaves(final Player player) {
final FightData data = FightData.getData(player);
data.angleHits.clear();
}
@EventHandler(priority = EventPriority.MONITOR)
@Override
public void playerLeaves(final Player player) {
final FightData data = FightData.getData(player);
data.angleHits.clear();
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerChangedWorld(final PlayerChangedWorldEvent event){
FightData.getData(event.getPlayer()).onWorldChange();
}
@EventHandler(ignoreCancelled = false, priority = EventPriority.MONITOR)
FightData.getData(event.getPlayer()).onWorldChange();
}
@EventHandler(ignoreCancelled = false, priority = EventPriority.MONITOR)
public void onItemHeld(final PlayerItemHeldEvent event) {
final Player player = event.getPlayer();
final long penalty = FightConfig.getConfig(player).toolChangeAttackPenalty;
if (penalty > 0 ) {
FightData.getData(player).attackPenalty.applyPenalty(penalty);
}
}
final Player player = event.getPlayer();
final long penalty = FightConfig.getConfig(player).toolChangeAttackPenalty;
if (penalty > 0 ) {
FightData.getData(player).attackPenalty.applyPenalty(penalty);
}
}
}

View File

@ -25,7 +25,7 @@ public class Reach extends Check {
/** The maximum distance allowed to interact with an entity in creative mode. */
public static final double CREATIVE_DISTANCE = 6D;
/** Additum for distance, based on entity. */
private static double getDistMod(final Entity damaged) {
// Handle the EnderDragon differently.
@ -55,7 +55,7 @@ public class Reach extends Check {
*/
public boolean check(final Player player, final Location pLoc, final Entity damaged, final Location dRef, final FightData data, final FightConfig cc) {
boolean cancel = false;
// The maximum distance allowed to interact with an entity in survival mode.
final double SURVIVAL_DISTANCE = cc.reachSurvivalDistance; // 4.4D;
// Amount which can be reduced by reach adaption.
@ -65,9 +65,9 @@ public class Reach extends Check {
final double distanceLimit = player.getGameMode() == GameMode.CREATIVE ? CREATIVE_DISTANCE : SURVIVAL_DISTANCE + getDistMod(damaged);
final double distanceMin = (distanceLimit - DYNAMIC_RANGE) / distanceLimit;
final double height = mcAccess.getHeight(damaged);
// Refine y position.
// TODO: Make a little more accurate by counting in the actual bounding box.
final double pY = pLoc.getY() + player.getEyeHeight();
@ -75,29 +75,29 @@ public class Reach extends Check {
if (pY <= dY); // Keep the foot level y.
else if (pY >= dY + height) dRef.setY(dY + height); // Highest ref y.
else dRef.setY(pY); // Level with damaged.
final Vector pRel = dRef.toVector().subtract(pLoc.toVector().setY(pY)); // TODO: Run calculations on numbers only :p.
// Distance is calculated from eye location to center of targeted. If the player is further away from their target
// than allowed, the difference will be assigned to "distance".
final double lenpRel = pRel.length();
double violation = lenpRel - distanceLimit;
final double reachMod = data.reachMod;
if (violation > 0) {
// They failed, increment violation level. This is influenced by lag, so don't do it if there was lag.
if (TickTask.getLag(1000, true) < 1.5f){
// TODO: 1.5 is a fantasy value.
data.reachVL += violation;
// TODO: 1.5 is a fantasy value.
data.reachVL += violation;
}
// Execute whatever actions are associated with this check and the violation level and find out if we should
// cancel the event.
cancel = executeActions(player, data.reachVL, violation, cc.reachActions);
if (Improbable.check(player, (float) violation / 2f, System.currentTimeMillis(), "fight.reach")){
cancel = true;
cancel = true;
}
if (cancel && cc.reachPenalty > 0){
// Apply an attack penalty time.
@ -105,36 +105,36 @@ public class Reach extends Check {
}
}
else if (lenpRel - distanceLimit * reachMod > 0){
// Silent cancel.
if (cc.reachPenalty > 0) {
data.attackPenalty.applyPenalty(cc.reachPenalty / 2);
}
// Silent cancel.
if (cc.reachPenalty > 0) {
data.attackPenalty.applyPenalty(cc.reachPenalty / 2);
}
cancel = true;
Improbable.feed(player, (float) (lenpRel - distanceLimit * reachMod) / 4f, System.currentTimeMillis());
}
else{
// Player passed the check, reward them.
data.reachVL *= 0.8D;
}
if (!cc.reachReduce){
data.reachMod = 1d;
data.reachMod = 1d;
}
else if (lenpRel > distanceLimit - DYNAMIC_RANGE){
data.reachMod = Math.max(distanceMin, data.reachMod - DYNAMIC_STEP);
data.reachMod = Math.max(distanceMin, data.reachMod - DYNAMIC_STEP);
}
else{
data.reachMod = Math.min(1.0, data.reachMod + DYNAMIC_STEP);
data.reachMod = Math.min(1.0, data.reachMod + DYNAMIC_STEP);
}
if (cc.debug && player.hasPermission(Permissions.ADMINISTRATION_DEBUG)){
player.sendMessage("NC+: Attack/reach " + damaged.getType()+ " height="+ StringUtil.fdec3.format(height) + " dist=" + StringUtil.fdec3.format(lenpRel) +" @" + StringUtil.fdec3.format(reachMod));
}
return cancel;
}
/**
* Data context for iterating over TraceEntry instances.
* @param player
@ -145,93 +145,93 @@ public class Reach extends Check {
* @param cc
* @return
*/
public ReachContext getContext(final Player player, final Location pLoc, final Entity damaged, final Location damagedLoc, final FightData data, final FightConfig cc) {
final ReachContext context = new ReachContext();
context.distanceLimit = player.getGameMode() == GameMode.CREATIVE ? CREATIVE_DISTANCE : cc.reachSurvivalDistance + getDistMod(damaged);
public ReachContext getContext(final Player player, final Location pLoc, final Entity damaged, final Location damagedLoc, final FightData data, final FightConfig cc) {
final ReachContext context = new ReachContext();
context.distanceLimit = player.getGameMode() == GameMode.CREATIVE ? CREATIVE_DISTANCE : cc.reachSurvivalDistance + getDistMod(damaged);
context.distanceMin = (context.distanceLimit - cc.reachReduceDistance) / context.distanceLimit;
context.damagedHeight = mcAccess.getHeight(damaged);
//context.eyeHeight = player.getEyeHeight();
context.pY = pLoc.getY() + player.getEyeHeight();
return context;
}
/**
* Check if the player fails the reach check, no change of FightData.
* @param player
* @param pLoc
* @param damaged
* @param dRef
* @param context
* @param data
* @param cc
* @return
*/
public boolean loopCheck(final Player player, final Location pLoc, final Entity damaged, final TraceEntry dRef, final ReachContext context, final FightData data, final FightConfig cc) {
boolean cancel = false;
return context;
}
/**
* Check if the player fails the reach check, no change of FightData.
* @param player
* @param pLoc
* @param damaged
* @param dRef
* @param context
* @param data
* @param cc
* @return
*/
public boolean loopCheck(final Player player, final Location pLoc, final Entity damaged, final TraceEntry dRef, final ReachContext context, final FightData data, final FightConfig cc) {
boolean cancel = false;
// Refine y position.
final double dY = dRef.y;
double y = dRef.y;
if (context.pY <= dY) {
// Keep the foot level y.
// Keep the foot level y.
}
else if (context.pY >= dY + context.damagedHeight) {
y = dY + context.damagedHeight; // Highest ref y.
y = dY + context.damagedHeight; // Highest ref y.
}
else {
y = context.pY; // Level with damaged.
y = context.pY; // Level with damaged.
}
// Distance is calculated from eye location to center of targeted. If the player is further away from their target
// than allowed, the difference will be assigned to "distance".
// TODO: Run check on squared distances (quite easy to change to stored boundary-sq values).
final double lenpRel = TrigUtil.distance(dRef.x, y, dRef.z, pLoc.getX(), context.pY, pLoc.getZ());
double violation = lenpRel - context.distanceLimit;
if (violation > 0 || lenpRel - context.distanceLimit * data.reachMod > 0){
// TODO: The silent cancel parts should be sen as "no violation" ?
// Set minimum violation in context
context.minViolation = Math.min(context.minViolation, lenpRel);
cancel = true;
// TODO: The silent cancel parts should be sen as "no violation" ?
// Set minimum violation in context
context.minViolation = Math.min(context.minViolation, lenpRel);
cancel = true;
}
context.minResult = Math.min(context.minResult, lenpRel);
return cancel;
}
/**
* Apply changes to FightData according to check results (context), trigger violations.
* @param player
* @param pLoc
* @param damaged
* @param context
* @param forceViolation
* @param data
* @param cc
* @return
*/
public boolean loopFinish(final Player player, final Location pLoc, final Entity damaged, final ReachContext context, final boolean forceViolation, final FightData data, final FightConfig cc) {
final double lenpRel = forceViolation && context.minViolation != Double.MAX_VALUE ? context.minViolation : context.minResult;
if (lenpRel == Double.MAX_VALUE) {
return false;
}
double violation = lenpRel - context.distanceLimit;
boolean cancel = false;
if (violation > 0) {
}
/**
* Apply changes to FightData according to check results (context), trigger violations.
* @param player
* @param pLoc
* @param damaged
* @param context
* @param forceViolation
* @param data
* @param cc
* @return
*/
public boolean loopFinish(final Player player, final Location pLoc, final Entity damaged, final ReachContext context, final boolean forceViolation, final FightData data, final FightConfig cc) {
final double lenpRel = forceViolation && context.minViolation != Double.MAX_VALUE ? context.minViolation : context.minResult;
if (lenpRel == Double.MAX_VALUE) {
return false;
}
double violation = lenpRel - context.distanceLimit;
boolean cancel = false;
if (violation > 0) {
// They failed, increment violation level. This is influenced by lag, so don't do it if there was lag.
if (TickTask.getLag(1000, true) < 1.5f){
// TODO: 1.5 is a fantasy value.
data.reachVL += violation;
// TODO: 1.5 is a fantasy value.
data.reachVL += violation;
}
// Execute whatever actions are associated with this check and the violation level and find out if we should
// cancel the event.
cancel = executeActions(player, data.reachVL, violation, cc.reachActions);
if (Improbable.check(player, (float) violation / 2f, System.currentTimeMillis(), "fight.reach")){
cancel = true;
cancel = true;
}
if (cancel && cc.reachPenalty > 0){
// Apply an attack penalty time.
@ -239,34 +239,34 @@ public class Reach extends Check {
}
}
else if (lenpRel - context.distanceLimit * data.reachMod > 0){
// Silent cancel.
if (cc.reachPenalty > 0) {
data.attackPenalty.applyPenalty(cc.reachPenalty / 2);
}
// Silent cancel.
if (cc.reachPenalty > 0) {
data.attackPenalty.applyPenalty(cc.reachPenalty / 2);
}
cancel = true;
Improbable.feed(player, (float) (lenpRel - context.distanceLimit * data.reachMod) / 4f, System.currentTimeMillis());
}
else{
// Player passed the check, reward them.
data.reachVL *= 0.8D;
}
// Adaption amount for dynamic range.
final double DYNAMIC_STEP = cc.reachReduceStep / cc.reachSurvivalDistance;
if (!cc.reachReduce){
data.reachMod = 1d;
data.reachMod = 1d;
}
else if (lenpRel > context.distanceLimit - cc.reachReduceDistance){
data.reachMod = Math.max(context.distanceMin, data.reachMod - DYNAMIC_STEP);
data.reachMod = Math.max(context.distanceMin, data.reachMod - DYNAMIC_STEP);
}
else{
data.reachMod = Math.min(1.0, data.reachMod + DYNAMIC_STEP);
data.reachMod = Math.min(1.0, data.reachMod + DYNAMIC_STEP);
}
if (cc.debug && player.hasPermission(Permissions.ADMINISTRATION_DEBUG)){
player.sendMessage("NC+: Attack/reach " + damaged.getType()+ " height="+ StringUtil.fdec3.format(context.damagedHeight) + " dist=" + StringUtil.fdec3.format(lenpRel) +" @" + StringUtil.fdec3.format(data.reachMod));
}
return cancel;
}
}
}

View File

@ -6,17 +6,17 @@ package fr.neatmonster.nocheatplus.checks.fight;
*
*/
public class ReachContext {
public double distanceLimit;
public double distanceMin;
public double damagedHeight;
/** Attacking player. */
public double eyeHeight;
/** Eye location y of the attacking player. */
public double pY;
/** Minimum value of lenpRel that was a violation. */
public double minViolation = Double.MAX_VALUE;
/** Minimum value of lenpRel. */
public double minResult = Double.MAX_VALUE;
public double distanceLimit;
public double distanceMin;
public double damagedHeight;
/** Attacking player. */
public double eyeHeight;
/** Eye location y of the attacking player. */
public double pY;
/** Minimum value of lenpRel that was a violation. */
public double minViolation = Double.MAX_VALUE;
/** Minimum value of lenpRel. */
public double minResult = Double.MAX_VALUE;
}

View File

@ -17,226 +17,225 @@ import fr.neatmonster.nocheatplus.utilities.TrigUtil;
*
*/
public class LocationTrace {
public static final class TraceEntry {
/** We keep it open, if ticks or ms are used. */
public long time;
/** Coordinates. */
public double x, y, z;
public double lastDistSq;
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;
}
}
/**
* Iterate from oldest to latest. Not a fully featured Iterator.
* @author mc_dev
*
*/
public static final class TraceIterator implements Iterator<TraceEntry>{
private final TraceEntry[] entries;
/** Index as in LocationTrace */
private final int index;
private final int size;
private int currentIndex;
private final boolean ascend;
protected TraceIterator(TraceEntry[] entries, int index, int size, int currentIndex, boolean ascend) {
if (currentIndex >= entries.length || currentIndex < 0 ||
currentIndex <= index - size || currentIndex > index && currentIndex <= index - size + entries.length) {
// This should also prevent iterators for size == 0, for the moment (!).
throw new IllegalArgumentException("startIndex out of bounds.");
}
this.entries = entries;
this.index = index;
this.size = size;
this.currentIndex = currentIndex;
this.ascend = ascend;
}
@Override
public final TraceEntry next() {
if (!hasNext()) {
throw new IndexOutOfBoundsException("No more entries to iterate.");
}
final TraceEntry entry = entries[currentIndex];
if (ascend) {
currentIndex ++;
if (currentIndex >= entries.length) {
currentIndex = 0;
}
int ref = index - size + 1;
if (ref < 0) {
ref += entries.length;
}
if (currentIndex == ref) {
// Invalidate the iterator.
currentIndex = -1;
}
} else {
currentIndex --;
if (currentIndex < 0) {
currentIndex = entries.length - 1;
}
if (currentIndex == index) {
// Invalidate the iterator.
currentIndex = - 1;
}
}
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. */
private final TraceEntry[] entries;
/** Last element index. */
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: 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) {
double lastDistSq = 0.0;
if (size > 0) {
final TraceEntry latestEntry = entries[index];
// TODO: Consider duration of staying there ?
if (x == latestEntry.x && y == latestEntry.y && z == latestEntry.z) {
latestEntry.time = time;
return;
}
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.
// Only merge if last distance was not greater than mergeDist, to prevent too-far-off entries.
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.
index++;
if (index == entries.length) {
index = 0;
}
if (size < entries.length) {
size ++;
}
final TraceEntry newEntry = entries[index];
newEntry.set(time, x, y, z, lastDistSq);
}
/** Reset content pointers - call with world changes. */
public void reset() {
index = 0;
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
*/
public TraceIterator latestIterator() {
return new TraceIterator(entries, index, size, index, false);
}
/**
* Iterate from oldest to latest.
* @return
*/
public TraceIterator oldestIterator() {
final int currentIndex = index - size + 1;
return new TraceIterator(entries, index, size, currentIndex < 0 ? currentIndex + entries.length : currentIndex, true);
}
/**
* Iterate from entry with max. age to latest, always includes latest.
* @param tick Absolute tick value for oldest accepted tick.
* @param ms Absolute ms value for oldest accepted ms;
* @return
*/
public TraceIterator maxAgeIterator(long time) {
int currentIndex = index;
int tempIndex = currentIndex;
int steps = 1;
while (steps < size) {
tempIndex --;
if (tempIndex < 0) {
tempIndex += size;
}
final TraceEntry entry = entries[tempIndex];
if (entry.time >= time) {
// Continue.
currentIndex = tempIndex;
} else {
break;
}
steps ++;
}
return new TraceIterator(entries, index, size, currentIndex, true);
}
public static final class TraceEntry {
/** We keep it open, if ticks or ms are used. */
public long time;
/** Coordinates. */
public double x, y, z;
public double lastDistSq;
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;
}
}
/**
* Iterate from oldest to latest. Not a fully featured Iterator.
* @author mc_dev
*
*/
public static final class TraceIterator implements Iterator<TraceEntry>{
private final TraceEntry[] entries;
/** Index as in LocationTrace */
private final int index;
private final int size;
private int currentIndex;
private final boolean ascend;
protected TraceIterator(TraceEntry[] entries, int index, int size, int currentIndex, boolean ascend) {
if (currentIndex >= entries.length || currentIndex < 0 ||
currentIndex <= index - size || currentIndex > index && currentIndex <= index - size + entries.length) {
// This should also prevent iterators for size == 0, for the moment (!).
throw new IllegalArgumentException("startIndex out of bounds.");
}
this.entries = entries;
this.index = index;
this.size = size;
this.currentIndex = currentIndex;
this.ascend = ascend;
}
@Override
public final TraceEntry next() {
if (!hasNext()) {
throw new IndexOutOfBoundsException("No more entries to iterate.");
}
final TraceEntry entry = entries[currentIndex];
if (ascend) {
currentIndex ++;
if (currentIndex >= entries.length) {
currentIndex = 0;
}
int ref = index - size + 1;
if (ref < 0) {
ref += entries.length;
}
if (currentIndex == ref) {
// Invalidate the iterator.
currentIndex = -1;
}
} else {
currentIndex --;
if (currentIndex < 0) {
currentIndex = entries.length - 1;
}
if (currentIndex == index) {
// Invalidate the iterator.
currentIndex = - 1;
}
}
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. */
private final TraceEntry[] entries;
/** Last element index. */
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: 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) {
double lastDistSq = 0.0;
if (size > 0) {
final TraceEntry latestEntry = entries[index];
// TODO: Consider duration of staying there ?
if (x == latestEntry.x && y == latestEntry.y && z == latestEntry.z) {
latestEntry.time = time;
return;
}
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.
// Only merge if last distance was not greater than mergeDist, to prevent too-far-off entries.
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.
index++;
if (index == entries.length) {
index = 0;
}
if (size < entries.length) {
size ++;
}
final TraceEntry newEntry = entries[index];
newEntry.set(time, x, y, z, lastDistSq);
}
/** Reset content pointers - call with world changes. */
public void reset() {
index = 0;
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
*/
public TraceIterator latestIterator() {
return new TraceIterator(entries, index, size, index, false);
}
/**
* Iterate from oldest to latest.
* @return
*/
public TraceIterator oldestIterator() {
final int currentIndex = index - size + 1;
return new TraceIterator(entries, index, size, currentIndex < 0 ? currentIndex + entries.length : currentIndex, true);
}
/**
* Iterate from entry with max. age to latest, always includes latest.
* @param time Absolute time value for oldest accepted entries.
* @return TraceIterator containing entries that have not been created before the given time, iterating ascending with time.
*/
public TraceIterator maxAgeIterator(long time) {
int currentIndex = index;
int tempIndex = currentIndex;
int steps = 1;
while (steps < size) {
tempIndex --;
if (tempIndex < 0) {
tempIndex += size;
}
final TraceEntry entry = entries[tempIndex];
if (entry.time >= time) {
// Continue.
currentIndex = tempIndex;
} else {
break;
}
steps ++;
}
return new TraceIterator(entries, index, size, currentIndex, true);
}
}

View File

@ -131,7 +131,7 @@ 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."*/
/** Moving trace (to-positions, use tick as time). This is initialized on "playerJoins, i.e. MONITOR, and set to null on playerLeaves."*/
private LocationTrace trace = null;
// sf rather