[BLEEDING] Prevent noclip using commands with untracked moves.

A cheat client could move such that they are inside of a block, but
CraftBukkit will not fire an event, because the distance and looking
direction don't change enough. Teleporting other players or yourself to
that location would result in getting someone into a block. Consequently
 we also have to block commands like /sethome at such locations.

Our first attempt to patch that will monitor teleports that use the
TeleportCause.COMMAND (might miss out on plugins that are not using the
appropriate cause, and on plugins that use items for teleportation), in
addition we monitor certain commands (configurable prefixes), to catch
things like "sethome" and "sethome2". The world spawn is exempted. Only
teleports into blocks are monitored.

This does not yet sanity-check the distance to the last tracked
location, but it will ignore if none is set.
This commit is contained in:
asofold 2014-12-17 19:50:45 +01:00
parent ea5b249197
commit fb6ac2ad5a
10 changed files with 407 additions and 177 deletions

View File

@ -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<N extends CharNode<N>, L extends CharLookupEntry<N>> extends PrefixTree<Character, N, L>{
public static class CharNode<N extends CharNode<N>> extends Node<Character, N>{
}
public static class SimpleCharNode extends CharNode<SimpleCharNode>{
}
public static class CharLookupEntry<N extends CharNode<N>> extends LookupEntry<Character, N>{
public CharLookupEntry(N node, N insertion, int depth, boolean hasPrefix){
super(node, insertion, depth, hasPrefix);
}
}
public CharPrefixTree(final NodeFactory<Character, N> nodeFactory, final LookupEntryFactory<Character, N, L> resultFactory) {
super(nodeFactory, resultFactory);
}
/**
* Auxiliary method to get a List of Character.
* @param chars
* @return
*/
public static final List<Character> toCharacterList(final char[] chars){
final List<Character> characters = new ArrayList<Character>(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<N extends CharNode<N>> extends Node<Character, N>{
}
/**
*
* @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<String> 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<String> 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<SimpleCharNode, CharLookupEntry<SimpleCharNode>> newCharPrefixTree(){
return new CharPrefixTree<SimpleCharNode, CharLookupEntry<SimpleCharNode>>(new NodeFactory<Character, SimpleCharNode>(){
@Override
public final SimpleCharNode newNode(final SimpleCharNode parent) {
return new SimpleCharNode();
}
}, new LookupEntryFactory<Character, SimpleCharNode, CharLookupEntry<SimpleCharNode>>() {
@Override
public final CharLookupEntry<SimpleCharNode> newLookupEntry(final SimpleCharNode node, final SimpleCharNode insertion, final int depth, final boolean hasPrefix) {
return new CharLookupEntry<SimpleCharNode>(node, insertion, depth, hasPrefix);
}
});
}
public static class SimpleCharNode extends CharNode<SimpleCharNode>{
}
public static class CharLookupEntry<N extends CharNode<N>> extends LookupEntry<Character, N>{
public CharLookupEntry(N node, N insertion, int depth, boolean hasPrefix){
super(node, insertion, depth, hasPrefix);
}
}
public CharPrefixTree(final NodeFactory<Character, N> nodeFactory, final LookupEntryFactory<Character, N, L> resultFactory) {
super(nodeFactory, resultFactory);
}
/**
* Auxiliary method to get a List of Character.
* @param chars
* @return
*/
public static final List<Character> toCharacterList(final char[] chars){
final List<Character> characters = new ArrayList<Character>(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<String> 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<String> 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<String> 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<SimpleCharNode, CharLookupEntry<SimpleCharNode>> newCharPrefixTree(){
return new CharPrefixTree<SimpleCharNode, CharLookupEntry<SimpleCharNode>>(new NodeFactory<Character, SimpleCharNode>(){
@Override
public final SimpleCharNode newNode(final SimpleCharNode parent) {
return new SimpleCharNode();
}
}, new LookupEntryFactory<Character, SimpleCharNode, CharLookupEntry<SimpleCharNode>>() {
@Override
public final CharLookupEntry<SimpleCharNode> newLookupEntry(final SimpleCharNode node, final SimpleCharNode insertion, final int depth, final boolean hasPrefix) {
return new CharLookupEntry<SimpleCharNode>(node, insertion, depth, hasPrefix);
}
});
}
}

View File

@ -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);

View File

@ -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.

View File

@ -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

View File

@ -129,7 +129,7 @@ public class MovingListener extends CheckListener implements TickListener, IRemo
private final Set<EntityType> normalVehicles = new HashSet<EntityType>();
/** 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;

View File

@ -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.
* <hr>
* Pre-conditions:<br>
* <li>Context-specific (e.g. activation flags for command, teleport).</li>
* <li>See MovingUtils.shouldCheckUntrackedLocation.</li>
*
* @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());
}
}
}

View File

@ -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";

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}
}