/* * This file is part of Hawk Anticheat. * Copyright (C) 2018 Hawk Development Team * * 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 <https://www.gnu.org/licenses/>. */ package me.islandscout.hawk.check.movement.position; import me.islandscout.hawk.util.*; import me.islandscout.hawk.HawkPlayer; import me.islandscout.hawk.check.MovementCheck; import me.islandscout.hawk.event.MoveEvent; import me.islandscout.hawk.wrap.entity.WrappedEntity; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Boat; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.util.Vector; import java.util.*; /** * In vanilla Minecraft, a free-falling player must fall a * specific distance for every succeeding move. Hawk's flight * check attempts to enforce this vanilla mechanic to prevent * players from using flight modifications. * <p> * For every succeeding move a free-falling player is in the * air, the player's vertical velocity is: * <p> * (v_(n-1) - 0.08) * 0.98 * <p> * A continuous function which describes a free-falling player's * vertical velocity given the amount of ticks passed is: * <p> * v(x) = (3.92 + v_i) * 0.98^x - 3.92 * <p> * A continuous function which describes a free-falling player's * vertical position given the amount of ticks passed is: * <p> * p(x) = -3.92(x+1) - 0.98^(x+1) * 50(3.92 + v_i) + 50(3.92 + v_i) + p_i */ public class FlyOld extends MovementCheck implements Listener { //TODO: Please. Just rewrite this. //TODO: I suggest getting rid of map lastDeltaY and instead use HawkPlayer#getVelocity(). Should keep things consistent, especially when moving from liquids to air. //TODO: false flag with pistons //TODO: false flag while jumping down stairs //TO DO: false flag when jumping on edge of block. Perhaps extrapolate next "noPos" moves until they touch the block, then reset expectedDeltaY //TODO: BYPASS! You can fly over fences. Jump, then toggle fly, then walk straight. //Don't change how you determine if on ground, even though that's what caused this. Instead, check when landing when deltaY > 0 //perhaps check if player's jump height is great enough? //TODO: You need to support "insignificant" moves private final Map<UUID, Double> lastDeltaY; private final Map<UUID, Location> legitLoc; private final Set<UUID> inAir; private final Map<UUID, Integer> stupidMoves; //private Map<UUID, List<Location>> locsOnPBlocks; private final Map<UUID, List<Pair<Double, Long>>> velocities; //launch velocities private final Set<UUID> failedSoDontUpdateRubberband; //Update rubberband loc until someone fails. In this case, do not update until they touch the ground. private static final int STUPID_MOVES = 1; //Apparently you can jump in midair right as you fall off the edge of a block. You need to time it right. public FlyOld() { super("fly", true, 0, 10, 0.995, 5000, "%player% failed fly. VL: %vl%", null); lastDeltaY = new HashMap<>(); inAir = new HashSet<>(); legitLoc = new HashMap<>(); stupidMoves = new HashMap<>(); //locsOnPBlocks = new HashMap<>(); velocities = new HashMap<>(); failedSoDontUpdateRubberband = new HashSet<>(); } @Override protected void check(MoveEvent event) { Player p = event.getPlayer(); HawkPlayer pp = event.getHawkPlayer(); double deltaY = event.getTo().getY() - event.getFrom().getY(); if (pp.hasFlyPending() && p.getAllowFlight()) return; if (!event.isOnGroundReally() && !pp.isFlying() && !p.isInsideVehicle() && !pp.isSwimming() && !p.isSleeping() && !isInClimbable(event.getTo()) && !isOnBoat(p, event.getTo())) { if (!inAir.contains(p.getUniqueId()) && deltaY > 0) lastDeltaY.put(p.getUniqueId(), 0.42 + getJumpBoostLvl(p) * 0.1); //handle any pending knockbacks if(event.hasAcceptedKnockback()) lastDeltaY.put(p.getUniqueId(), deltaY); if(event.isSlimeBlockBounce()) lastDeltaY.put(p.getUniqueId(), deltaY); double expectedDeltaY = lastDeltaY.getOrDefault(p.getUniqueId(), 0D); double epsilon = 0.03; //lastDeltaY.put(p.getUniqueId(), (lastDeltaY.getOrDefault(p.getUniqueId(), 0D) - 0.025) * 0.8); //water function if (WrappedEntity.getWrappedEntity(p).getCollisionBox(event.getFrom().toVector()).getMaterials(p.getWorld()).contains(Material.WEB)) { lastDeltaY.put(p.getUniqueId(), -0.007); epsilon = 0.000001; if (AdjacentBlocks.onGroundReally(event.getTo().clone().add(0, -0.03, 0), -1, false, 0.02, pp)) return; } else if(!pp.isInWater() && event.isInWater()) { //entering liquid lastDeltaY.put(p.getUniqueId(), (lastDeltaY.getOrDefault(p.getUniqueId(), 0D) * 0.98) - 0.038399); } else { //in air lastDeltaY.put(p.getUniqueId(), (lastDeltaY.getOrDefault(p.getUniqueId(), 0D) - 0.08) * 0.98); } //handle teleport if (event.hasTeleported()) { lastDeltaY.put(p.getUniqueId(), 0D); expectedDeltaY = 0; legitLoc.put(p.getUniqueId(), event.getTo()); } if (deltaY - expectedDeltaY > epsilon && event.hasDeltaPos()) { //oopsie daisy. client made a goof up //wait one little second: minecraft is being a pain in the ass and it wants to play tricks when you parkour on the very edge of blocks //we need to check this first... if (deltaY < 0) { Location checkLoc = event.getFrom().clone(); checkLoc.setY(event.getTo().getY()); if (AdjacentBlocks.onGroundReally(checkLoc, deltaY, false, 0.02, pp)) { onGroundStuff(p); return; } //extrapolate move BEFORE getFrom, then check checkLoc.setY(event.getFrom().getY()); checkLoc.setX(checkLoc.getX() - (event.getTo().getX() - event.getFrom().getX())); checkLoc.setZ(checkLoc.getZ() - (event.getTo().getZ() - event.getFrom().getZ())); if (AdjacentBlocks.onGroundReally(checkLoc, deltaY, false, 0.02, pp)) { onGroundStuff(p); return; } } if(event.isOnClientBlock() != null) { onGroundStuff(p); return; } //scold the child punish(pp, false, event); tryRubberband(event); lastDeltaY.put(p.getUniqueId(), canCancel() ? 0 : deltaY); failedSoDontUpdateRubberband.add(p.getUniqueId()); return; } reward(pp); //the player is in air now, since they have a positive Y velocity and they're not on the ground if (inAir.contains(p.getUniqueId())) //upwards now stupidMoves.put(p.getUniqueId(), 0); //handle stupid moves, because the client tends to want to jump a little late if you jump off the edge of a block if (stupidMoves.getOrDefault(p.getUniqueId(), 0) >= STUPID_MOVES || (deltaY > 0 && AdjacentBlocks.onGroundReally(event.getFrom(), -1, true, 0.02, pp))) //falling now inAir.add(p.getUniqueId()); stupidMoves.put(p.getUniqueId(), stupidMoves.getOrDefault(p.getUniqueId(), 0) + 1); } else { onGroundStuff(p); } if (!failedSoDontUpdateRubberband.contains(p.getUniqueId()) || event.isOnGroundReally()) { legitLoc.put(p.getUniqueId(), p.getLocation()); failedSoDontUpdateRubberband.remove(p.getUniqueId()); } } private void onGroundStuff(Player p) { lastDeltaY.put(p.getUniqueId(), 0D); inAir.remove(p.getUniqueId()); stupidMoves.put(p.getUniqueId(), 0); } private boolean isOnBoat(Player p, Location loc) { Set<Entity> trackedEntities = hawk.getLagCompensator().getPositionTrackedEntities(); int ping = ServerUtils.getPing(p); for(Entity entity : trackedEntities) { if (entity instanceof Boat) { AABB boatBB = WrappedEntity.getWrappedEntity(entity).getCollisionBox(hawk.getLagCompensator().getHistoryLocation(ping, entity).toVector()); AABB feet = new AABB( new Vector(-0.3, -0.4, -0.3).add(loc.toVector()), new Vector(0.3, 0, 0.3).add(loc.toVector())); if (feet.isColliding(boatBB)) return true; } } return false; } private boolean isInClimbable(Location loc) { Block b = ServerUtils.getBlockAsync(loc); return b != null && (b.getType() == Material.VINE || b.getType() == Material.LADDER); } private int getJumpBoostLvl(Player p) { for (PotionEffect pEffect : p.getActivePotionEffects()) { if (pEffect.getType().equals(PotionEffectType.JUMP)) { return pEffect.getAmplifier() + 1; } } return 0; } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onTeleport(PlayerTeleportEvent e) { HawkPlayer pp = hawk.getHawkPlayer(e.getPlayer()); legitLoc.put(pp.getUuid(), e.getTo()); } @Override public void removeData(Player p) { UUID uuid = p.getUniqueId(); lastDeltaY.remove(uuid); inAir.remove(uuid); legitLoc.remove(uuid); stupidMoves.remove(uuid); velocities.remove(uuid); failedSoDontUpdateRubberband.remove(uuid); } }