/** * Various corner cases that would cause this check to fail or require special treatment * * @param player * @param data * @param from * @param to * @return */ public boolean shouldBeApplied( final Player player, final MovingData data, final Location from, final Location to) { if (player.isDead() || player.isInsideVehicle() || data.insideVehicle) return false; if (data.wasTeleported) { // Remember this location data.teleportedTo = from.clone(); data.wasTeleported = false; data.jumpPhase = 0; } if (data.teleportedTo != null && data.teleportedTo.getWorld().equals(from.getWorld())) { // As long as the from-Location doesn't change, the player didn't accept the teleport if (data.teleportedTo.distanceSquared(from) < 0.01D) { // Event after Teleport ignored return false; } else { // The player finally accepted the teleport with the previous event data.teleportedTo = null; } } // If the target is a bed, don't check (going to bed is a kind of mini teleport...) if (to.getWorld().getBlockTypeIdAt(to) == Material.BED_BLOCK.getId()) { return false; } return true; }
/** * Update the cached values for players velocity to be prepared to give them additional movement * freedom in their next move events * * @param v * @param data */ public void updateVelocity(Vector v, MovingData data) { // Compare the velocity vector to the existing movement freedom that we've from previous events double tmp = (Math.abs(v.getX()) + Math.abs(v.getZ())) * 3D; if (tmp > data.horizFreedom) data.horizFreedom = tmp; if (v.getY() > data.maxYVelocity) { data.maxYVelocity = v.getY(); } }
/** * Register a task with bukkit that will be run a short time from now, displaying how many * violations happened in that timeframe * * @param p * @param data */ private void setupSummaryTask(final Player p, final MovingData data) { // Setup task to display summary later if (data.summaryTask == -1) { Runnable r = new Runnable() { @Override public void run() { if (data.highestLogLevel != null) { String logString = String.format( summaryMessage, p.getName(), ticksBeforeSummary / 20, data.violationsInARow[0], data.violationsInARow[1], data.violationsInARow[2]); plugin.log(data.highestLogLevel, logString); data.highestLogLevel = Level.ALL; } // deleting its own reference data.summaryTask = -1; data.violationsInARow[0] = 0; data.violationsInARow[1] = 0; data.violationsInARow[2] = 0; } }; // Give a summary in x ticks. 20 ticks ~ 1 second data.summaryTask = plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, r, ticksBeforeSummary); } }
/** * Call this when a player got successfully teleported with the corresponding event to adjust * stored data to the new situation * * @param event */ public void teleported(PlayerTeleportEvent event) { MovingData data = MovingData.get(event.getPlayer()); // We can enforce a teleport, if that flag is explicitly set (but I'd rather have other plugins // not arbitrarily cancel teleport events in the first place... if (data.teleportInitializedByMe != null && event.isCancelled() && enforceTeleport && event.getTo().equals(data.teleportInitializedByMe)) { event.setCancelled(false); data.teleportInitializedByMe = null; } if (!event.isCancelled()) { data.wasTeleported = true; data.setBackPoint = event.getTo().clone(); // data.lastLocation = event.getTo().clone(); } // reset anyway - if another plugin cancelled our teleport it's no use to try and be precise data.jumpPhase = 0; }
public PreciseLocation check(NoCheatPlayer player, MovingData data, CCMoving ccmoving) { final PreciseLocation setBack = data.runflySetBackPoint; final PreciseLocation from = data.from; final PreciseLocation to = data.to; if (!setBack.isSet()) { setBack.set(from); } final double yDistance = to.y - from.y; // Calculate some distances final double xDistance = to.x - from.x; final double zDistance = to.z - from.z; final double horizontalDistance = Math.sqrt((xDistance * xDistance + zDistance * zDistance)); double resultHoriz = 0; double resultVert = 0; double result = 0; PreciseLocation newToLocation = null; // In case of creative gamemode, give at least 0.60 speed limit // horizontal double speedLimitHorizontal = player.isCreative() ? Math.max(creativeSpeed, ccmoving.flyingSpeedLimitHorizontal) : ccmoving.flyingSpeedLimitHorizontal; speedLimitHorizontal *= player.getSpeedAmplifier(); resultHoriz = Math.max(0.0D, horizontalDistance - data.horizFreedom - speedLimitHorizontal); boolean sprinting = player.isSprinting(); data.bunnyhopdelay--; // Did he go too far? if (resultHoriz > 0 && sprinting) { // Try to treat it as a the "bunnyhop" problem if (data.bunnyhopdelay <= 0 && resultHoriz < 0.4D) { data.bunnyhopdelay = 3; resultHoriz = 0; } } resultHoriz *= 100; // super simple, just check distance compared to max distance resultVert = Math.max(0.0D, yDistance - data.vertFreedom - ccmoving.flyingSpeedLimitVertical) * 100; result = resultHoriz + resultVert; if (result > 0) { // Increment violation counter data.runflyVL += result; if (resultHoriz > 0) { data.runflyRunningTotalVL += resultHoriz; data.runflyRunningFailed++; } if (resultVert > 0) { data.runflyFlyingTotalVL += resultVert; data.runflyFlyingFailed++; } boolean cancel = executeActions(player, ccmoving.flyingActions.getActions(data.runflyVL)); // Was one of the actions a cancel? Then really do it if (cancel) { newToLocation = setBack; } } // Slowly reduce the level with each event data.runflyVL *= 0.97; // Some other cleanup 'n' stuff if (newToLocation == null) { setBack.set(to); } return newToLocation; }
/** * The actual check. First find out if the event needs to be handled at all Second check if the * player moved too far horizontally Third check if the player moved too high vertically Fourth * treat any occured violations as configured * * @param event */ public Location check(Player player, Location from, Location to, MovingData data) { updateVelocity(player.getVelocity(), data); Location newToLocation = null; final long startTime = System.nanoTime(); /** *********** DECIDE WHICH CHECKS NEED TO BE RUN ************ */ final boolean flyCheck = !allowFlying && !plugin.hasPermission(player, PermissionData.PERMISSION_FLYING, checkOPs); final boolean runCheck = true; /** *************** REFINE EVENT DATA FOR CHECKS ************** */ if (flyCheck || runCheck) { // In both cases it will be interesting to know the type of underground the player // is in or goes to final int fromType = helper.isLocationOnGround(from.getWorld(), from.getX(), from.getY(), from.getZ(), false); final int toType = helper.isLocationOnGround(to.getWorld(), to.getX(), to.getY(), to.getZ(), false); final boolean fromOnGround = fromType != MovingEventHelper.NONSOLID; final boolean toOnGround = toType != MovingEventHelper.NONSOLID; // Distribute data to checks in the form needed by the checks /** ******************* EXECUTE THE CHECKS ******************* */ double result = 0.0D; if (flyCheck) { result += Math.max(0D, flyingCheck.check(player, from, fromOnGround, to, toOnGround, data)); } if (runCheck) { result += Math.max( 0D, runningCheck.check( from, to, !allowFakeSneak && player.isSneaking(), !allowFastSwim && (fromType & toType & MovingEventHelper.LIQUID) > 0, data)); } /** ******* HANDLE/COMBINE THE RESULTS OF THE CHECKS ********** */ data.jumpPhase++; if (result <= 0) { if (fromOnGround) { data.setBackPoint = from; data.jumpPhase = 0; } else if (toOnGround) { data.jumpPhase = 0; } } else if (result > 0) { // Increment violation counter data.violationLevel += result; if (data.setBackPoint == null) data.setBackPoint = from; } if (result > 0 && data.violationLevel > 1) { setupSummaryTask(player, data); int level = limitCheck(data.violationLevel - 1); data.violationsInARow[level]++; newToLocation = action(player, from, to, actions[level], data.violationsInARow[level], data); } } // Slowly reduce the level with each event data.violationLevel *= 0.97; data.horizFreedom *= 0.97; statisticElapsedTimeNano += System.nanoTime() - startTime; statisticTotalEvents++; return newToLocation; }
/** * Perform actions that were specified in the config file * * @param event * @param action * @return */ private Location action( Player player, Location from, Location to, Action[] actions, int violations, MovingData data) { Location newToLocation = null; if (actions == null) return newToLocation; boolean cancelled = false; for (Action a : actions) { if (a.firstAfter <= violations) { if (a.firstAfter == violations || a.repeat) { if (a instanceof LogAction) { // prepare log message if necessary String log = String.format( Locale.US, logMessage, player.getName(), from.getWorld().getName(), to.getWorld().getName(), from.getX(), from.getY(), from.getZ(), to.getX(), to.getY(), to.getZ(), Math.abs(from.getX() - to.getX()), to.getY() - from.getY(), Math.abs(from.getZ() - to.getZ())); plugin.log(((LogAction) a).level, log); // Remember the highest log level we encountered to determine what level the summary log // message should have if (data.highestLogLevel == null) data.highestLogLevel = Level.ALL; if (data.highestLogLevel.intValue() < ((LogAction) a).level.intValue()) data.highestLogLevel = ((LogAction) a).level; } else if (!cancelled && a instanceof CancelAction) { // Make a modified copy of the setBackPoint to prevent other plugins from accidentally // modifying it // and keep the current pitch and yaw (setbacks "feel" better that way). Plus try to // adapt the Y-coord // to place the player close to ground double y = data.setBackPoint.getY(); // search for the first solid block up to 5 blocks below the setbackpoint and teleport // the player there int i = 0; for (; i < 20; i++) { if (playerIsOnGround(data.setBackPoint, -0.5 * i) != MovingData.NONSOLID) { break; } } y -= 0.5 * i; data.setBackPoint.setY(y); // Remember the location we send the player to, to identify teleports that were started // by us data.teleportInitializedByMe = new Location( data.setBackPoint.getWorld(), data.setBackPoint.getX(), y, data.setBackPoint.getZ(), to.getYaw(), to.getPitch()); newToLocation = data.teleportInitializedByMe; cancelled = true; // just prevent us from treating more than one "cancel" action, which would // make no sense } else if (a instanceof CustomAction) plugin.handleCustomAction((CustomAction) a, player); } } } return newToLocation; }