diff --git a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/prefixtree/CharPrefixTree.java b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/prefixtree/CharPrefixTree.java index 434b6108..35957f7e 100644 --- a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/prefixtree/CharPrefixTree.java +++ b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/prefixtree/CharPrefixTree.java @@ -8,164 +8,178 @@ import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.CharPrefixTree.CharLoo import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.CharPrefixTree.CharNode; public class CharPrefixTree, L extends CharLookupEntry> extends PrefixTree{ - - public static class CharNode> extends Node{ - } - - public static class SimpleCharNode extends CharNode{ - } - - public static class CharLookupEntry> extends LookupEntry{ - public CharLookupEntry(N node, N insertion, int depth, boolean hasPrefix){ - super(node, insertion, depth, hasPrefix); - } - } - - public CharPrefixTree(final NodeFactory nodeFactory, final LookupEntryFactory resultFactory) { - super(nodeFactory, resultFactory); - } - /** - * Auxiliary method to get a List of Character. - * @param chars - * @return - */ - public static final List toCharacterList(final char[] chars){ - final List characters = new ArrayList(chars.length); - for (int i = 0; i < chars.length; i++){ - characters.add(chars[i]); - } - return characters; - } - - /** - * - * @param chars - * @param create - * @return - */ - public L lookup(final char[] chars, final boolean create){ - return lookup(toCharacterList(chars), create); - } - - /** - * - * @param chars - * @param create - * @return - */ - public L lookup(final String input, final boolean create){ - return lookup(input.toCharArray(), create); - } - - /** - * - * @param chars - * @return If already inside (not necessarily as former end point). - */ - public boolean feed(final String input){ - return feed(input.toCharArray()); - } + public static class CharNode> extends Node{ + } - /** - * - * @param chars - * @return If already inside (not necessarily as former end point). - */ - public boolean feed(final char[] chars){ - return feed(toCharacterList(chars)); - } - - public void feedAll(final Collection inputs, final boolean trim, final boolean lowerCase){ - for (String input : inputs){ - if (trim) input = input.toLowerCase(); - if (lowerCase) input = input.toLowerCase(); - feed(input); - } - } - - public boolean hasPrefix(final char[] chars){ - return hasPrefix(toCharacterList(chars)); - } - - public boolean hasPrefix(final String input){ - return hasPrefix(input.toCharArray()); - } - - /** - * Quick and dirty addition: Test if a prefix is contained which either matches the whole input or does not end inside of a word in the input, i.e. the inputs next character is a space. - * @param input - * @return - */ - public boolean hasPrefixWords(final String input) { - // TODO build this in in a more general way (super classes + stop symbol)! - final L result = lookup(input, false); - if (!result.hasPrefix) return false; - if (input.length() == result.depth) return true; - if (Character.isWhitespace(input.charAt(result.depth))) return true; - return false; - } - - /** - * Test hasPrefixWords for each given argument. - * @param inputs - * @return true if hasPrefixWords(String) returns true for any of the inputs, false otherwise. - */ - public boolean hasAnyPrefixWords(final String... inputs){ - for (int i = 0; i < inputs.length; i++){ - if (hasPrefixWords(inputs[i])){ - return true; - } - } - return false; - } - - /** - * Test hasPrefixWords for each element of the collection. - * @param inputs - * @return true if hasPrefixWords(String) returns true for any of the elements, false otherwise. - */ - public boolean hasAnyPrefixWords(final Collection inputs){ - for (final String input : inputs){ - if (hasPrefixWords(input)){ - return true; - } - } - return false; - } - - public boolean isPrefix(final char[] chars){ - return isPrefix(toCharacterList(chars)); - } - - public boolean isPrefix(final String input){ - return isPrefix(input.toCharArray()); - } - - public boolean matches(final char[] chars){ - return matches(toCharacterList(chars)); - } - - public boolean matches(final String input){ - return matches(input.toCharArray()); - } - - /** - * Factory method for a simple tree. - * @param keyType - * @return - */ - public static CharPrefixTree> newCharPrefixTree(){ - return new CharPrefixTree>(new NodeFactory(){ - @Override - public final SimpleCharNode newNode(final SimpleCharNode parent) { - return new SimpleCharNode(); - } - }, new LookupEntryFactory>() { - @Override - public final CharLookupEntry newLookupEntry(final SimpleCharNode node, final SimpleCharNode insertion, final int depth, final boolean hasPrefix) { - return new CharLookupEntry(node, insertion, depth, hasPrefix); - } - }); - } + public static class SimpleCharNode extends CharNode{ + } + + public static class CharLookupEntry> extends LookupEntry{ + public CharLookupEntry(N node, N insertion, int depth, boolean hasPrefix){ + super(node, insertion, depth, hasPrefix); + } + } + + public CharPrefixTree(final NodeFactory nodeFactory, final LookupEntryFactory resultFactory) { + super(nodeFactory, resultFactory); + } + + /** + * Auxiliary method to get a List of Character. + * @param chars + * @return + */ + public static final List toCharacterList(final char[] chars){ + final List characters = new ArrayList(chars.length); + for (int i = 0; i < chars.length; i++){ + characters.add(chars[i]); + } + return characters; + } + + /** + * + * @param chars + * @param create + * @return + */ + public L lookup(final char[] chars, final boolean create){ + return lookup(toCharacterList(chars), create); + } + + /** + * + * @param chars + * @param create + * @return + */ + public L lookup(final String input, final boolean create){ + return lookup(input.toCharArray(), create); + } + + /** + * + * @param chars + * @return If already inside (not necessarily as former end point). + */ + public boolean feed(final String input){ + return feed(input.toCharArray()); + } + + /** + * + * @param chars + * @return If already inside (not necessarily as former end point). + */ + public boolean feed(final char[] chars){ + return feed(toCharacterList(chars)); + } + + public void feedAll(final Collection inputs, final boolean trim, final boolean lowerCase){ + for (String input : inputs){ + if (trim) input = input.toLowerCase(); + if (lowerCase) input = input.toLowerCase(); + feed(input); + } + } + + public boolean hasPrefix(final char[] chars){ + return hasPrefix(toCharacterList(chars)); + } + + public boolean hasPrefix(final String input){ + return hasPrefix(input.toCharArray()); + } + + /** + * Quick and dirty addition: Test if a prefix is contained which either matches the whole input or does not end inside of a word in the input, i.e. the inputs next character is a space. + * @param input + * @return + */ + public boolean hasPrefixWords(final String input) { + // TODO build this in in a more general way (super classes + stop symbol)! + final L result = lookup(input, false); + if (!result.hasPrefix) return false; + if (input.length() == result.depth) return true; + if (Character.isWhitespace(input.charAt(result.depth))) return true; + return false; + } + + /** + * Test hasPrefixWords for each given argument. + * @param inputs + * @return true if hasPrefixWords(String) returns true for any of the inputs, false otherwise. + */ + public boolean hasAnyPrefixWords(final String... inputs){ + for (int i = 0; i < inputs.length; i++){ + if (hasPrefixWords(inputs[i])){ + return true; + } + } + return false; + } + + /** + * Test hasPrefixWords for each element of the collection. + * @param inputs + * @return true if hasPrefixWords(String) returns true for any of the elements, false otherwise. + */ + public boolean hasAnyPrefixWords(final Collection inputs){ + for (final String input : inputs){ + if (hasPrefixWords(input)){ + return true; + } + } + return false; + } + + /** + * Test if there is an end-point in the tree that is a prefix of any of the inputs. + * @param inputs + * @return + */ + public boolean hasAnyPrefix(final Collection inputs) { + for (final String input : inputs) { + if (hasPrefix(input)) { + return true; + } + } + return false; + } + + public boolean isPrefix(final char[] chars){ + return isPrefix(toCharacterList(chars)); + } + + public boolean isPrefix(final String input){ + return isPrefix(input.toCharArray()); + } + + public boolean matches(final char[] chars){ + return matches(toCharacterList(chars)); + } + + public boolean matches(final String input){ + return matches(input.toCharArray()); + } + + /** + * Factory method for a simple tree. + * @param keyType + * @return + */ + public static CharPrefixTree> newCharPrefixTree(){ + return new CharPrefixTree>(new NodeFactory(){ + @Override + public final SimpleCharNode newNode(final SimpleCharNode parent) { + return new SimpleCharNode(); + } + }, new LookupEntryFactory>() { + @Override + public final CharLookupEntry newLookupEntry(final SimpleCharNode node, final SimpleCharNode insertion, final int depth, final boolean hasPrefix) { + return new CharLookupEntry(node, insertion, depth, hasPrefix); + } + }); + } } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java index dabde546..91b2ea85 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java @@ -3,6 +3,7 @@ package fr.neatmonster.nocheatplus.checks.chat; import java.util.ArrayList; import java.util.List; +import org.bukkit.Location; import org.bukkit.command.Command; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -12,15 +13,20 @@ import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerLoginEvent.Result; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import fr.neatmonster.nocheatplus.NCPAPIProvider; import fr.neatmonster.nocheatplus.checks.CheckListener; import fr.neatmonster.nocheatplus.checks.CheckType; +import fr.neatmonster.nocheatplus.checks.moving.MovingConfig; +import fr.neatmonster.nocheatplus.checks.moving.MovingUtil; import fr.neatmonster.nocheatplus.command.CommandUtil; import fr.neatmonster.nocheatplus.components.INotifyReload; import fr.neatmonster.nocheatplus.components.JoinLeaveListener; import fr.neatmonster.nocheatplus.config.ConfPaths; import fr.neatmonster.nocheatplus.config.ConfigFile; import fr.neatmonster.nocheatplus.config.ConfigManager; +import fr.neatmonster.nocheatplus.logging.Streams; import fr.neatmonster.nocheatplus.utilities.StringUtil; import fr.neatmonster.nocheatplus.utilities.TickTask; import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.SimpleCharPrefixTree; @@ -63,6 +69,9 @@ public class ChatListener extends CheckListener implements INotifyReload, JoinLe /** Commands not to be executed in-game. */ private final SimpleCharPrefixTree consoleOnlyCommands = new SimpleCharPrefixTree(); + /** Set world to null after use, primary thread only. */ + private final Location useLoc = new Location(null, 0, 0, 0); + public ChatListener() { super(CheckType.CHAT); ConfigFile config = ConfigManager.getConfigFile(); @@ -176,10 +185,39 @@ public class ChatListener extends CheckListener implements INotifyReload, JoinLe // Treat as command. if (commands.isEnabled(player) && commands.check(player, checkMessage, captcha)) { event.setCancelled(true); + } else { + // TODO: Consider always checking these? + // Note that this checks for prefixes, not prefix words. + final MovingConfig mcc = MovingConfig.getConfig(player); + if (mcc.passableUntrackedCommandCheck && mcc.passableUntrackedCommandPrefixes.hasAnyPrefix(messageVars)) { + if (checkUntrackedLocation(player, message, mcc)) { + event.setCancelled(true); + } + } } } } + + private boolean checkUntrackedLocation(final Player player, final String message, final MovingConfig mcc) { + final Location loc = player.getLocation(useLoc); + boolean cancel = false; + if (MovingUtil.shouldCheckUntrackedLocation(player, loc)) { + final Location newTo = MovingUtil.checkUntrackedLocation(loc); + if (newTo != null) { + if (mcc.passableUntrackedCommandTryTeleport && player.teleport(newTo, TeleportCause.PLUGIN)) { + NCPAPIProvider.getNoCheatPlusAPI().getLogManager().info(Streams.TRACE_FILE, player.getName() + " runs the command '" + message + "' at an untracked location: " + loc + " , teleport to: " + newTo); + } else { + // TODO: Allow disabling cancel? + // TODO: Should message the player? + NCPAPIProvider.getNoCheatPlusAPI().getLogManager().info(Streams.TRACE_FILE, player.getName() + " runs the command '" + message + "' at an untracked location: " + loc + " , cancel the command."); + cancel = true; + } + } + } + useLoc.setWorld(null); // Cleanup. + return cancel; + } private boolean textChecks(final Player player, final String message, final boolean isMainThread, final boolean alreadyCancelled) { return text.isEnabled(player) && text.check(player, message, captcha, isMainThread, alreadyCancelled); diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingConfig.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingConfig.java index a124c371..d9ba4b40 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingConfig.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingConfig.java @@ -5,6 +5,7 @@ import java.util.Map; import org.bukkit.entity.Player; +import fr.neatmonster.nocheatplus.NCPAPIProvider; import fr.neatmonster.nocheatplus.actions.ActionList; import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.access.ACheckConfig; @@ -13,7 +14,9 @@ import fr.neatmonster.nocheatplus.checks.access.ICheckConfig; import fr.neatmonster.nocheatplus.config.ConfPaths; import fr.neatmonster.nocheatplus.config.ConfigFile; import fr.neatmonster.nocheatplus.config.ConfigManager; +import fr.neatmonster.nocheatplus.logging.Streams; import fr.neatmonster.nocheatplus.permissions.Permissions; +import fr.neatmonster.nocheatplus.utilities.ds.prefixtree.SimpleCharPrefixTree; /** * Configurations specific for the moving checks. Every world gets one of these assigned to it. @@ -107,6 +110,10 @@ public class MovingConfig extends ACheckConfig { public final boolean passableRayTracingBlockChangeOnly; // TODO: passableAccuracy: also use if not using ray-tracing public final ActionList passableActions; + public final boolean passableUntrackedTeleportCheck; + public final boolean passableUntrackedCommandCheck; + public final boolean passableUntrackedCommandTryTeleport; + public final SimpleCharPrefixTree passableUntrackedCommandPrefixes = new SimpleCharPrefixTree(); public final boolean survivalFlyCheck; public final int survivalFlyBlockingSpeed; @@ -198,6 +205,18 @@ public class MovingConfig extends ACheckConfig { passableRayTracingCheck = config.getBoolean(ConfPaths.MOVING_PASSABLE_RAYTRACING_CHECK); passableRayTracingBlockChangeOnly = config.getBoolean(ConfPaths.MOVING_PASSABLE_RAYTRACING_BLOCKCHANGEONLY); passableActions = config.getOptimizedActionList(ConfPaths.MOVING_PASSABLE_ACTIONS, Permissions.MOVING_PASSABLE); + passableUntrackedTeleportCheck = config.getBoolean(ConfPaths.MOVING_PASSABLE_UNTRACKED_TELEPORT_ACTIVE); + passableUntrackedCommandCheck = config.getBoolean(ConfPaths.MOVING_PASSABLE_UNTRACKED_CMD_ACTIVE); + passableUntrackedCommandTryTeleport = config.getBoolean(ConfPaths.MOVING_PASSABLE_UNTRACKED_CMD_TRYTELEPORT); + try { + for (String prefix : config.getStringList(ConfPaths.MOVING_PASSABLE_UNTRACKED_CMD_PREFIXES)) { + if (prefix != null && !prefix.isEmpty()) { + passableUntrackedCommandPrefixes.feed(prefix.toLowerCase()); + } + } + } catch (Exception e) { + NCPAPIProvider.getNoCheatPlusAPI().getLogManager().warning(Streams.STATUS, "[NoCheatPlus] Bad prefixes definition (String list) for " + ConfPaths.MOVING_PASSABLE_UNTRACKED_CMD_PREFIXES); + } survivalFlyCheck = config.getBoolean(ConfPaths.MOVING_SURVIVALFLY_CHECK); // Default values are specified here because this settings aren't showed by default into the configuration file. diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingData.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingData.java index d95590f3..8ac262b2 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingData.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingData.java @@ -127,11 +127,11 @@ public class MovingData extends ACheckData { private final AxisVelocity horVel = new AxisVelocity(); // Coordinates. - /** Last from coordinates. */ + /** Last from coordinates. X is at Double.MAX_VALUE, if not set. */ public double fromX = Double.MAX_VALUE, fromY, fromZ; - /** Last to coordinates. */ + /** Last to coordinates. X is at Double.MAX_VALUE, if not set. */ public double toX = Double.MAX_VALUE, toY, toZ; - /** Moving trace (to-positions, use tick as time). 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 diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingListener.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingListener.java index 8b7bf007..be53e166 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingListener.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingListener.java @@ -129,7 +129,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo private final Set normalVehicles = new HashSet(); /** Location for temporary use with getLocation(useLoc). Always call setWorld(null) after use. Use LocUtil.clone before passing to other API. */ - private final Location useLoc = new Location(null, 0, 0, 0); // TODO: Put to use... + final Location useLoc = new Location(null, 0, 0, 0); // TODO: Put to use... /** Statistics / debugging counters. */ private final Counters counters = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class); @@ -494,7 +494,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo else{ checkCf = checkSf = false; } - + boolean checkNf = true; if (checkSf || checkCf) { // Check jumping on things like slime blocks. @@ -628,7 +628,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo * @param cc */ private void processTrampoline(final Player player, final PlayerLocation from, final PlayerLocation to, final MovingData data, final MovingConfig cc) { - + // TODO: Consider making this just a checking method (use result for applying effects). // CHEATING: Add velocity. // TODO: 1. Confine for direct use (no latency here). 2. Hard set velocity? 3.. Switch to friction based. @@ -858,7 +858,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo final Location teleported = data.getTeleported(); // If it was a teleport initialized by NoCheatPlus, do it anyway even if another plugin said "no". - final Location to = event.getTo(); + Location to = event.getTo(); final Location ref; if (teleported != null && teleported.equals(to)) { // Teleport by NCP. @@ -915,6 +915,22 @@ public class MovingListener extends CheckListener implements TickListener, IRemo // pass = true; } } + else if (cause == TeleportCause.COMMAND) { + // Attempt to prevent teleporting to players inside of blocks at untracked coordinates. + // TODO: Consider checking this on low or lowest (!). + // TODO: Other like TeleportCause.PLUGIN? + if (cc.passableUntrackedTeleportCheck && MovingUtil.shouldCheckUntrackedLocation(player, to)) { + final Location newTo = MovingUtil.checkUntrackedLocation(to); + if (newTo != null) { + // Adjust the teleport to go to the last tracked to-location of the other player. + to = newTo; + event.setTo(newTo); + cancel = smallRange = false; + // TODO: Consider console, consider data.debug. + NCPAPIProvider.getNoCheatPlusAPI().getLogManager().warning(Streams.TRACE_FILE, player.getName() + " correct untracked teleport destination (" + to + " corrected to " + newTo + ")."); + } + } + } // if (pass) { // ref = to; diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingUtil.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingUtil.java index c9c303c5..33517972 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingUtil.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/MovingUtil.java @@ -1,8 +1,11 @@ package fr.neatmonster.nocheatplus.checks.moving; +import org.bukkit.Chunk; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerMoveEvent; @@ -14,6 +17,7 @@ import fr.neatmonster.nocheatplus.permissions.Permissions; import fr.neatmonster.nocheatplus.utilities.BlockProperties; import fr.neatmonster.nocheatplus.utilities.CheckUtils; import fr.neatmonster.nocheatplus.utilities.PlayerLocation; +import fr.neatmonster.nocheatplus.utilities.TrigUtil; /** * Static utility methods. @@ -22,6 +26,11 @@ import fr.neatmonster.nocheatplus.utilities.PlayerLocation; */ public class MovingUtil { + /** + * Always set world to null after use, careful with nested methods. Main thread only. + */ + private static final Location useLoc = new Location(null, 0, 0, 0); + /** * Check if the player is to be checked by the survivalfly check. * @param player @@ -89,4 +98,79 @@ public class MovingUtil { return BlockProperties.isGround(blockType) || BlockProperties.isSolid(blockType); } + /** + * Check the context-independent pre-conditions for checking for untracked + * locations (not the world spawn, location is not passable, passable is + * enabled for the player). + * + * @param player + * @param loc + * @return + */ + public static boolean shouldCheckUntrackedLocation(final Player player, final Location loc) { + return !TrigUtil.isSamePos(loc, loc.getWorld().getSpawnLocation()) + && !BlockProperties.isPassable(loc) + && CheckType.MOVING_PASSABLE.isEnabled(player); + } + + /** + * Detect if the given location is an untracked spot. This is spots for + * which a player is at the location, but the moving data has another + * "last to" position set for that player. Note that one matching player + * with "last to" being consistent is enough to let this return null, world spawn is exempted. + *
+ * Pre-conditions:
+ *
  • Context-specific (e.g. activation flags for command, teleport).
  • + *
  • See MovingUtils.shouldCheckUntrackedLocation.
  • + * + * @param loc + * @return Corrected location, if loc is an "untracked location". + */ + public static Location checkUntrackedLocation(final Location loc) { + // TODO: More efficient method to get entities at the same position (might use MCAccess). + final Chunk toChunk = loc.getChunk(); + final Entity[] entities = toChunk.getEntities(); + MovingData untrackedData = null; + for (int i = 0; i < entities.length; i++) { + final Entity entity = entities[i]; + if (entity.getType() != EntityType.PLAYER) { + continue; + } + final Location refLoc = entity.getLocation(useLoc); + // Exempt world spawn. + // TODO: Exempt other warps -> HASH based exemption (expire by time, keep high count)? + if (TrigUtil.isSamePos(loc, refLoc) && (entity instanceof Player)) { + final Player other = (Player) entity; + final MovingData otherData = MovingData.getData(other); + if (otherData.toX == Double.MAX_VALUE) { + // Data might have been removed. + // TODO: Consider counting as tracked? + continue; + } + else if (TrigUtil.isSamePos(refLoc, otherData.toX, otherData.toY, otherData.toZ)) { + // Tracked. + return null; + } + else { + // Untracked location. + // TODO: Discard locations in the same block, if passable. + // TODO: Sanity check distance? + // More leniency: allow moving inside of the same block. + if (TrigUtil.isSameBlock(loc, otherData.toX, otherData.toY, otherData.toZ) && !BlockProperties.isPassable(refLoc.getWorld(), otherData.toX, otherData.toY, otherData.toZ)) { + continue; + } + untrackedData = otherData; + } + } + } + useLoc.setWorld(null); // Cleanup. + if (untrackedData == null) { + return null; + } + else { + // TODO: Count and log to TRACE_FILE, if multiple locations would match (!). + return new Location(loc.getWorld(), untrackedData.toX, untrackedData.toY, untrackedData.toZ, loc.getYaw(), loc.getPitch()); + } + } + } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java index 2aa289dc..53624966 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java @@ -527,12 +527,19 @@ public abstract class ConfPaths { public static final String MOVING_NOFALL_ANTICRITICALS = MOVING_NOFALL + "anticriticals"; public static final String MOVING_NOFALL_ACTIONS = MOVING_NOFALL + "actions"; - public static final String MOVING_PASSABLE = MOVING + "passable."; - public static final String MOVING_PASSABLE_CHECK = MOVING_PASSABLE + "active"; - private static final String MOVING_PASSABLE_RAYTRACING = MOVING_PASSABLE + "raytracing."; - public static final String MOVING_PASSABLE_RAYTRACING_CHECK = MOVING_PASSABLE_RAYTRACING + "active"; - public static final String MOVING_PASSABLE_RAYTRACING_BLOCKCHANGEONLY= MOVING_PASSABLE_RAYTRACING + "blockchangeonly"; - public static final String MOVING_PASSABLE_ACTIONS = MOVING_PASSABLE + "actions"; + public static final String MOVING_PASSABLE = MOVING + "passable."; + public static final String MOVING_PASSABLE_CHECK = MOVING_PASSABLE + "active"; + private static final String MOVING_PASSABLE_RAYTRACING = MOVING_PASSABLE + "raytracing."; + public static final String MOVING_PASSABLE_RAYTRACING_CHECK = MOVING_PASSABLE_RAYTRACING + "active"; + public static final String MOVING_PASSABLE_RAYTRACING_BLOCKCHANGEONLY = MOVING_PASSABLE_RAYTRACING + "blockchangeonly"; + public static final String MOVING_PASSABLE_ACTIONS = MOVING_PASSABLE + "actions"; + private static final String MOVING_PASSABLE_UNTRACKED = MOVING_PASSABLE + "untracked."; + private static final String MOVING_PASSABLE_UNTRACKED_TELEPORT = MOVING_PASSABLE_UNTRACKED + "teleport."; + public static final String MOVING_PASSABLE_UNTRACKED_TELEPORT_ACTIVE = MOVING_PASSABLE_UNTRACKED_TELEPORT + "active"; + private static final String MOVING_PASSABLE_UNTRACKED_CMD = MOVING_PASSABLE_UNTRACKED + "command."; + public static final String MOVING_PASSABLE_UNTRACKED_CMD_ACTIVE = MOVING_PASSABLE_UNTRACKED_CMD + "active"; + public static final String MOVING_PASSABLE_UNTRACKED_CMD_TRYTELEPORT = MOVING_PASSABLE_UNTRACKED_CMD + "tryteleport"; + public static final String MOVING_PASSABLE_UNTRACKED_CMD_PREFIXES = MOVING_PASSABLE_UNTRACKED_CMD + "prefixes"; private static final String MOVING_SURVIVALFLY = MOVING + "survivalfly."; public static final String MOVING_SURVIVALFLY_CHECK = MOVING_SURVIVALFLY + "active"; diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java index da34250b..1e3d0e1f 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java @@ -381,6 +381,10 @@ public class DefaultConfig extends ConfigFile { set(ConfPaths.MOVING_PASSABLE_RAYTRACING_CHECK, true); set(ConfPaths.MOVING_PASSABLE_RAYTRACING_BLOCKCHANGEONLY, false); set(ConfPaths.MOVING_PASSABLE_ACTIONS, "cancel vl>10 log:passable:0:5:if cancel vl>50 log:passable:0:5:icf cancel"); + set(ConfPaths.MOVING_PASSABLE_UNTRACKED_TELEPORT_ACTIVE, true); + set(ConfPaths.MOVING_PASSABLE_UNTRACKED_CMD_ACTIVE, true); + set(ConfPaths.MOVING_PASSABLE_UNTRACKED_CMD_TRYTELEPORT, true); + set(ConfPaths.MOVING_PASSABLE_UNTRACKED_CMD_PREFIXES, Arrays.asList("sethome", "home set", "setwarp", "warp set", "setback", "set back", "back set")); set(ConfPaths.MOVING_SURVIVALFLY_CHECK, true); // set(ConfPaths.MOVING_SURVIVALFLY_EXTENDED_HACC, false); diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/BlockProperties.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/BlockProperties.java index 18e29284..b6e28a7d 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/BlockProperties.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/BlockProperties.java @@ -14,6 +14,7 @@ import java.util.Map.Entry; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.World; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; @@ -373,13 +374,13 @@ public class BlockProperties { /** Fence gate style with 0x04 being fully passable. */ public static final long F_PASSABLE_X4 = 0x200000; - + // TODO: Separate no fall damage flag ? [-> on ground could return "dominating" flags, or extra flags] /** Like slime block: bounce back 25% of fall height without taking fall damage [TODO: Check/adjust]. */ public static final long F_BOUNCE25 = 0x400000; // TODO: When flags are out, switch to per-block classes :p. - + /** * Map flag to names. */ @@ -1886,13 +1887,25 @@ public class BlockProperties { } /** - * Convenience method for debugging purposes. + * Convenience method. * @param loc * @return */ public static final boolean isPassable(final Location loc) { - blockCache.setAccess(loc.getWorld()); - boolean res = isPassable(blockCache, loc.getX(), loc.getY(), loc.getZ(), blockCache.getTypeId(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + return isPassable(loc.getWorld(), loc.getX(), loc.getY(), loc.getZ()); + } + + /** + * Convenience method. + * @param world + * @param x + * @param y + * @param z + * @return + */ + public static final boolean isPassable(final World world, final double x, final double y, final double z) { + blockCache.setAccess(world); + boolean res = isPassable(blockCache, x, y, z, blockCache.getTypeId(x, y, z)); blockCache.cleanup(); return res; } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/TrigUtil.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/TrigUtil.java index 08d1647d..dc259844 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/TrigUtil.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/utilities/TrigUtil.java @@ -391,7 +391,7 @@ public class TrigUtil { else if (yawDiff > 180f) yawDiff -= 360f; return yawDiff; } - + /** * Manhattan distance. * @param loc1 @@ -478,4 +478,39 @@ public class TrigUtil { || xDistance > 0D && zDistance > 0D && yaw > 90F && yaw < 180F; } + /** + * Test if both locations have the exact same coordinates. Does not check yaw/pitch. + * @param loc1 + * @param loc2 + * @return Returns false if either is null. + */ + public static boolean isSamePos(final Location loc1, final Location loc2) { + if (loc1 == null || loc2 == null) { + return false; + } + return loc1.getX() == loc2.getX() && loc1.getZ() == loc2.getZ() && loc1.getY() == loc2.getY(); + } + + /** + * Test if the location has the given coordinates. + * @param loc + * @param x + * @param y + * @param z + * @return Returns false if loc is null; + */ + public static boolean isSamePos(final Location loc, final double x, final double y, final double z) { + if (loc == null) { + return false; + } + return loc.getX() == x && loc.getZ() == z && loc.getY() == y; + } + + public static boolean isSameBlock(final Location loc, final double x, final double y, final double z) { + if (loc == null) { + return false; + } + return loc.getBlockX() == Location.locToBlock(x) && loc.getBlockZ() == Location.locToBlock(z) && loc.getBlockY() == Location.locToBlock(y); + } + }