From 3a1e9fe7d8062659badcb48364f81d93839103e1 Mon Sep 17 00:00:00 2001 From: asofold Date: Tue, 6 Nov 2012 07:56:53 +0100 Subject: [PATCH] Safety fuck up commit. --- .../nocheatplus/actions/ParameterName.java | 3 +- .../nocheatplus/checks/CheckType.java | 139 ++++--- .../checks/combined/CombinedListener.java | 2 +- .../checks/fight/FightListener.java | 24 +- .../nocheatplus/checks/moving/MovingData.java | 22 +- .../checks/moving/MovingListener.java | 184 ++++++---- .../nocheatplus/checks/moving/Passable.java | 100 +++--- .../checks/moving/SurvivalFly.java | 314 +++++++++------- .../command/admin/RemovePlayerCommand.java | 2 +- .../nocheatplus/components/IRemoveData.java | 3 +- .../hooks/NCPExemptionManager.java | 57 ++- .../permissions/PermissionUtil.java | 67 ++++ .../nocheatplus/players/DataManager.java | 73 +++- .../nocheatplus/players/Permissions.java | 24 +- .../utilities/ActionFrequency.java | 10 +- .../nocheatplus/utilities/BlockCache.java | 94 ++--- .../utilities/BlockProperties.java | 19 +- .../nocheatplus/utilities/CheckUtils.java | 338 +++++++++--------- .../nocheatplus/utilities/PlayerLocation.java | 7 +- .../nocheatplus/utilities/ds/CoordMap.java | 162 +++++++++ 20 files changed, 1025 insertions(+), 619 deletions(-) create mode 100644 src/fr/neatmonster/nocheatplus/permissions/PermissionUtil.java create mode 100644 src/fr/neatmonster/nocheatplus/utilities/ds/CoordMap.java diff --git a/src/fr/neatmonster/nocheatplus/actions/ParameterName.java b/src/fr/neatmonster/nocheatplus/actions/ParameterName.java index f546d625..01f78a59 100644 --- a/src/fr/neatmonster/nocheatplus/actions/ParameterName.java +++ b/src/fr/neatmonster/nocheatplus/actions/ParameterName.java @@ -21,8 +21,9 @@ package fr.neatmonster.nocheatplus.actions; * Some wildcards that are used in commands and log messages. */ public enum ParameterName { - CHECK("check"), BLOCK_ID("blockid"), + CHECK("check"), + TAGS("tags"), DISTANCE("distance"), FALL_DISTANCE("falldistance"), FOOD("food"), diff --git a/src/fr/neatmonster/nocheatplus/checks/CheckType.java b/src/fr/neatmonster/nocheatplus/checks/CheckType.java index e51e170f..d67dadcf 100644 --- a/src/fr/neatmonster/nocheatplus/checks/CheckType.java +++ b/src/fr/neatmonster/nocheatplus/checks/CheckType.java @@ -1,8 +1,5 @@ package fr.neatmonster.nocheatplus.checks; -import java.util.HashSet; -import java.util.Set; - import org.bukkit.entity.Player; import fr.neatmonster.nocheatplus.checks.access.CheckConfigFactory; @@ -23,7 +20,7 @@ import fr.neatmonster.nocheatplus.checks.inventory.InventoryConfig; import fr.neatmonster.nocheatplus.checks.inventory.InventoryData; import fr.neatmonster.nocheatplus.checks.moving.MovingConfig; import fr.neatmonster.nocheatplus.checks.moving.MovingData; -import fr.neatmonster.nocheatplus.hooks.APIUtils; +import fr.neatmonster.nocheatplus.players.DataManager; import fr.neatmonster.nocheatplus.players.Permissions; /* @@ -40,9 +37,9 @@ import fr.neatmonster.nocheatplus.players.Permissions; * Type of checks (containing configuration and dataFactory classes, name and permission). */ public enum CheckType { - ALL, + ALL(Permissions.CHECKS), - BLOCKBREAK(BlockBreakConfig.factory, BlockBreakData.factory), + BLOCKBREAK(BlockBreakConfig.factory, BlockBreakData.factory, Permissions.BLOCKBREAK), BLOCKBREAK_DIRECTION(BLOCKBREAK, Permissions.BLOCKBREAK_DIRECTION), BLOCKBREAK_FASTBREAK(BLOCKBREAK, Permissions.BLOCKBREAK_FASTBREAK), BLOCKBREAK_FREQUENCY(BLOCKBREAK, Permissions.BLOCKBREAK_FREQUENCY), @@ -50,18 +47,18 @@ public enum CheckType { BLOCKBREAK_REACH(BLOCKBREAK, Permissions.BLOCKBREAK_REACH), BLOCKBREAK_WRONGBLOCK(BLOCKBREAK, Permissions.BLOCKBREAK_WRONGBLOCK), - BLOCKINTERACT(BlockInteractConfig.factory, BlockInteractData.factory), + BLOCKINTERACT(BlockInteractConfig.factory, BlockInteractData.factory, Permissions.BLOCKINTERACT), BLOCKINTERACT_DIRECTION(BLOCKINTERACT, Permissions.BLOCKINTERACT_DIRECTION), BLOCKINTERACT_REACH(BLOCKINTERACT, Permissions.BLOCKINTERACT_REACH), - BLOCKPLACE(BlockPlaceConfig.factory, BlockPlaceData.factory), + BLOCKPLACE(BlockPlaceConfig.factory, BlockPlaceData.factory, Permissions.BLOCKPLACE), BLOCKPLACE_DIRECTION(BLOCKPLACE, Permissions.BLOCKPLACE_DIRECTION), BLOCKPLACE_FASTPLACE(BLOCKPLACE, Permissions.BLOCKPLACE_FASTPLACE), BLOCKPLACE_NOSWING(BLOCKPLACE, Permissions.BLOCKPLACE_NOSWING), BLOCKPLACE_REACH(BLOCKPLACE, Permissions.BLOCKBREAK_REACH), BLOCKPLACE_SPEED(BLOCKPLACE, Permissions.BLOCKPLACE_SPEED), - CHAT(ChatConfig.factory, ChatData.factory), + CHAT(ChatConfig.factory, ChatData.factory, Permissions.CHAT), CHAT_CAPTCHA(CHAT, Permissions.CHAT_CAPTCHA), CHAT_COLOR(CHAT, Permissions.CHAT_COLOR), CHAT_COMMANDS(CHAT, Permissions.CHAT_COMMANDS), @@ -70,10 +67,10 @@ public enum CheckType { CHAT_RELOG(CHAT, Permissions.CHAT_RELOG), - COMBINED(CombinedConfig.factory, CombinedData.factory), + COMBINED(CombinedConfig.factory, CombinedData.factory, Permissions.COMBINED), COMBINED_IMPROBABLE(COMBINED, Permissions.COMBINED_IMPROBABLE), - FIGHT(FightConfig.factory, FightData.factory), + FIGHT(FightConfig.factory, FightData.factory, Permissions.FIGHT), FIGHT_ANGLE(FIGHT, Permissions.FIGHT_ANGLE), FIGHT_CRITICAL(FIGHT, Permissions.FIGHT_CRITICAL), FIGHT_DIRECTION(FIGHT, Permissions.FIGHT_DIRECTION), @@ -84,14 +81,14 @@ public enum CheckType { FIGHT_SELFHIT(FIGHT, Permissions.FIGHT_SELFHIT), FIGHT_SPEED(FIGHT, Permissions.FIGHT_SPEED), - INVENTORY(InventoryConfig.factory, InventoryData.factory), + INVENTORY(InventoryConfig.factory, InventoryData.factory, Permissions.INVENTORY), INVENTORY_DROP(INVENTORY, Permissions.INVENTORY_DROP), INVENTORY_FASTCLICK(INVENTORY, Permissions.INVENTORY_FASTCLICK), INVENTORY_INSTANTBOW(INVENTORY, Permissions.INVENTORY_INSTANTBOW), INVENTORY_INSTANTEAT(INVENTORY, Permissions.INVENTORY_INSTANTEAT), INVENTORY_ITEMS(INVENTORY, Permissions.INVENTORY_ITEMS), - MOVING(MovingConfig.factory, MovingData.factory), + MOVING(MovingConfig.factory, MovingData.factory, Permissions.MOVING), MOVING_CREATIVEFLY(MOVING, Permissions.MOVING_CREATIVEFLY), MOVING_MOREPACKETS(MOVING, Permissions.MOVING_MOREPACKETS), MOVING_MOREPACKETSVEHICLE(MOVING, Permissions.MOVING_MOREPACKETSVEHICLE), @@ -101,50 +98,65 @@ public enum CheckType { UNKNOWN; - /** The group. */ - private CheckType parent = null; + /** If not null, this is the check group usually. */ + private final CheckType parent; - /** The configFactory. */ - private CheckConfigFactory configFactory = null; + /** The check config factory (access CheckConfig instances by CheckType). */ + private final CheckConfigFactory configFactory; - /** The dataFactory. */ - private CheckDataFactory dataFactory = null; + /** The check data factory (access CheckData instances by CheckType). */ + private final CheckDataFactory dataFactory; - /** The permission. */ - private String permission = null; + /** The bypass permission. */ + private final String permission; - /** - * Instantiates a new check type. - */ - private CheckType() {} + /** + * Special purpose check types (likely not actual checks). + */ + private CheckType() { + this(null, null, null); + } + + /** + * Special purpose for grouping (ALL). + * @param permission + */ + private CheckType(final String permission){ + this(null, null, permission); + } + + /** + * Constructor for root checks or check groups, that are not grouped under another check type. + * @param configFactory + * @param dataFactory + * @param permission + */ + private CheckType(final CheckConfigFactory configFactory, final CheckDataFactory dataFactory, final String permission) { + this(null, permission, configFactory, dataFactory); + } - /** - * Instantiates a new check type. - * - * @param configFactory - * the configFactory - * @param dataFactory - * the dataFactory - */ - private CheckType(final CheckConfigFactory configFactory, final CheckDataFactory dataFactory) { - this.configFactory = configFactory; - this.dataFactory = dataFactory; - } + /** + * Constructor for sub-checks grouped under another check type. + * @param parent + * @param permission + */ + private CheckType(final CheckType parent, final String permission) { + this(parent, permission, parent.getConfigFactory(), parent.getDataFactory()); + } - /** - * Instantiates a new check type. - * - * @param parent - * the parent - * @param permission - * the permission - */ - private CheckType(final CheckType parent, final String permission) { - this.parent = parent; - configFactory = parent.getConfigFactory(); - dataFactory = parent.getDataFactory(); - this.permission = permission; - } + /** + * Root constructor. + * @param parent Super check type (usually the group). + * @param permission Bypass permission. + * @param configFactory Check config factory. + * @param dataFactory Check data factory. + */ + private CheckType(final CheckType parent, final String permission, final CheckConfigFactory configFactory, final CheckDataFactory dataFactory) { + this.parent = parent; + this.permission = permission; + this.configFactory = configFactory; + this.dataFactory = dataFactory; + } /** * Gets the configFactory. @@ -183,7 +195,7 @@ public enum CheckType { } /** - * Gets the permission. + * Gets the bypass permission for this check type. * * @return the permission */ @@ -223,29 +235,12 @@ public enum CheckType { /** * Remove the player data for a given player and a given check type. CheckType.ALL and null will be interpreted as removing all data.
+ * @deprecated Will be removed, use DataManager.removeData instead. * @param playerName * @param checkType * @return If any data was present. */ - public static boolean removeData(final String playerName, CheckType checkType) { - if (checkType == null) checkType = ALL; - - // Attempt for direct removal. - CheckDataFactory dataFactory = checkType.getDataFactory(); - if (dataFactory != null) return dataFactory.removeData(playerName) != null; - - // Remove all for which it seems necessary. - final Set factories = new HashSet(); - for (CheckType otherType : CheckType.values()){ - if (checkType == ALL || APIUtils.isParent(checkType, otherType)){ - final CheckDataFactory otherFactory = otherType.getDataFactory(); - if (otherFactory != null) factories.add(otherFactory); - } - } - boolean had = false; - for (final CheckDataFactory otherFactory : factories){ - if (otherFactory.removeData(playerName) != null) had = true; - } - return had; + public static boolean removeData(final String playerName, final CheckType checkType) { + return DataManager.removeData(playerName, checkType); } } \ No newline at end of file diff --git a/src/fr/neatmonster/nocheatplus/checks/combined/CombinedListener.java b/src/fr/neatmonster/nocheatplus/checks/combined/CombinedListener.java index ac5d8d8c..950b528f 100644 --- a/src/fr/neatmonster/nocheatplus/checks/combined/CombinedListener.java +++ b/src/fr/neatmonster/nocheatplus/checks/combined/CombinedListener.java @@ -54,7 +54,7 @@ public class CombinedListener implements Listener { } } - @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false) + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onEntityDamage(final EntityDamageEvent event){ final Entity entity = event.getEntity(); if (!(entity instanceof Player)) return; diff --git a/src/fr/neatmonster/nocheatplus/checks/fight/FightListener.java b/src/fr/neatmonster/nocheatplus/checks/fight/FightListener.java index e1a9da50..a04772c6 100644 --- a/src/fr/neatmonster/nocheatplus/checks/fight/FightListener.java +++ b/src/fr/neatmonster/nocheatplus/checks/fight/FightListener.java @@ -116,20 +116,16 @@ public class FightListener implements Listener { cancelled = true; } - - if (!cancelled && angle.isEnabled(player)){ - // Improbable yaw. - if (Combined.checkYawRate(player, player.getLocation().getYaw(), now, worldName, cc.yawRateCheck)){ - // (Check or just feed). - cancelled = true; - } - // Angle check. - if (angle.check(player, worldChanged)) cancelled = true; - } - else{ - // Always feed yaw rate here. - Combined.feedYawRate(player, player.getLocation().getYaw(), now, worldName); - } + if (angle.isEnabled(player)) { + // The "fast turning" checks are checked in any case because they accumulate data. + // Improbable yaw changing. + if (Combined.checkYawRate(player, player.getLocation().getYaw(), now, worldName, cc.yawRateCheck)) { + // (Check or just feed). + cancelled = true; + } + // Angle check. + if (angle.check(player, worldChanged)) cancelled = true; + } if (!cancelled && critical.isEnabled(player) && critical.check(player)) cancelled = true; diff --git a/src/fr/neatmonster/nocheatplus/checks/moving/MovingData.java b/src/fr/neatmonster/nocheatplus/checks/moving/MovingData.java index fc647d0b..aec7f64d 100644 --- a/src/fr/neatmonster/nocheatplus/checks/moving/MovingData.java +++ b/src/fr/neatmonster/nocheatplus/checks/moving/MovingData.java @@ -87,7 +87,9 @@ public class MovingData extends ACheckData { /** Last from coordinates. */ public double fromX = Double.MAX_VALUE, fromY, fromZ; /** Last to coordinates. */ - public double toX = Double.MAX_VALUE, toY, toZ; + public double toX = Double.MAX_VALUE, toY, toZ; + /** To was ground or web etc. */ + public boolean toWasReset; // Data of the creative check. public boolean creativeFlyPreviousRefused; @@ -152,6 +154,24 @@ public class MovingData extends ACheckData { clearAccounting(); clearNoFallData(); } + + /** + * Mildly reset the flying data without losing any important information. + * + * @param setBack + */ + public void onSetBack(final Location setBack) { + // Reset positions + resetPositions(teleported); + this.setBack = teleported; + clearAccounting(); // Might be more safe to do this. + // Keep no-fall data. + // Fly data: problem is we don't remember the settings for the set back location. + // Assume the player to start falling from there rather, or be on ground. + // Keep jump amplifier + // Keep bunny-hop delay (?) + // keep jump phase. + } public void clearAccounting() { final long now = System.currentTimeMillis(); diff --git a/src/fr/neatmonster/nocheatplus/checks/moving/MovingListener.java b/src/fr/neatmonster/nocheatplus/checks/moving/MovingListener.java index 69fab1db..67805ba1 100644 --- a/src/fr/neatmonster/nocheatplus/checks/moving/MovingListener.java +++ b/src/fr/neatmonster/nocheatplus/checks/moving/MovingListener.java @@ -26,6 +26,7 @@ import org.bukkit.event.player.PlayerBedLeaveEvent; import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerGameModeChangeEvent; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerKickEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerPortalEvent; @@ -208,9 +209,18 @@ public class MovingListener implements Listener { final Player player = event.getPlayer(); final MovingData data = MovingData.getData(player); - if (!creativeFly.isEnabled(player) && survivalFly.isEnabled(player) && survivalFly.check(player) && data.ground != null) - // To cancel the event, we simply teleport the player to his last safe location. - player.teleport(data.ground); + if (!creativeFly.isEnabled(player) && survivalFly.isEnabled(player) && survivalFly.check(player)) { + // To cancel the event, we simply teleport the player to his last + // safe location. + Location target = null; + if (data.ground != null) target = data.ground; + else if (data.setBack != null) target = data.setBack; +// else target = player.getLocation(); // TODO + + + if (target != null) player.teleport(target);// TODO: schedule / other measures ? + } + } /** @@ -350,20 +360,11 @@ public class MovingListener implements Listener { data.noFallAssumeGround = false; data.teleported = null; - final EntityPlayer mcPlayer = ((CraftPlayer) player).getHandle(); - // Potion effect "Jump". - final double jumpAmplifier; - if (mcPlayer.hasEffect(MobEffectList.JUMP)) { -// final int amplifier = mcPlayer.getEffect(MobEffectList.JUMP).getAmplifier(); -// if (amplifier > 20) -// jumpAmplifier = 1.5D * (amplifier + 1D); -// else -// jumpAmplifier = 1.2D * (amplifier + 1D); - jumpAmplifier = 1D + mcPlayer.getEffect(MobEffectList.JUMP).getAmplifier(); - if (cc.debug) System.out.println(player.getName() + " Jump effect: " + data.jumpAmplifier); - } - else jumpAmplifier = 1D; - if (jumpAmplifier > data.jumpAmplifier) data.jumpAmplifier = jumpAmplifier; + final EntityPlayer mcPlayer = ((CraftPlayer) player).getHandle(); + // Potion effect "Jump". + final double jumpAmplifier = MovingListener.getJumpAmplifier(mcPlayer); + if (jumpAmplifier > 0D && cc.debug) System.out.println(player.getName() + " Jump effect: " + data.jumpAmplifier); + if (jumpAmplifier > data.jumpAmplifier) data.jumpAmplifier = jumpAmplifier; // Just try to estimate velocities over time. Not very precise, but works good enough most of the time. Do // general data modifications one for each event. @@ -386,13 +387,18 @@ public class MovingListener implements Listener { } - Location newTo = null; - - if (passable.isEnabled(player)) newTo = passable.check(player, pFrom, pTo, data, cc); + Location newTo = null; + + final Location passableTo; + // Check passable in any case (!) + if (cc.passableCheck && !NCPExemptionManager.isExempted(player, CheckType.MOVING_PASSABLE) && !player.hasPermission(Permissions.MOVING_PASSABLE)) { + // Passable is checked first to get the original set-back locations from the other checks, if needed. + passableTo = passable.check(player, pFrom, pTo, data, cc); + } + else passableTo = null; // Optimized checking, giving creativefly permission precedence over survivalfly. - if (newTo != null); - else if (!player.hasPermission(Permissions.MOVING_CREATIVEFLY)){ + if (!player.hasPermission(Permissions.MOVING_CREATIVEFLY)){ // Either survivalfly or speed check. if ((cc.ignoreCreative || player.getGameMode() != GameMode.CREATIVE) && (cc.ignoreAllowFlight || !player.getAllowFlight()) && cc.survivalFlyCheck && !NCPExemptionManager.isExempted(player, CheckType.MOVING_SURVIVALFLY) && !player.hasPermission(Permissions.MOVING_SURVIVALFLY)){ @@ -411,13 +417,16 @@ public class MovingListener implements Listener { } else data.clearFlyData(); - if (newTo == null - && cc.morePacketsCheck && !NCPExemptionManager.isExempted(player, CheckType.MOVING_MOREPACKETS) && !player.hasPermission(Permissions.MOVING_MOREPACKETS)) - // If he hasn't been stopped by any other check and is handled by the more packets check, execute it. - newTo = morePackets.check(player, pFrom, pTo, data, cc); - else - // Otherwise we need to clear his data. - data.clearMorePacketsData(); + if (newTo == null && cc.morePacketsCheck && !NCPExemptionManager.isExempted(player, CheckType.MOVING_MOREPACKETS) && !player.hasPermission(Permissions.MOVING_MOREPACKETS)) { + // If he hasn't been stopped by any other check and is handled by the more packets check, execute it. + newTo = morePackets.check(player, pFrom, pTo, data, cc); + } else { + // Otherwise we need to clear his data. + data.clearMorePacketsData(); + } + + // Prefer the location returned by passable. + if (passableTo != null) newTo = passableTo; // Did one of the checks decide we need a new "to"-location? if (newTo != null) { @@ -427,18 +436,21 @@ public class MovingListener implements Listener { // Remember where we send the player to. data.teleported = newTo; } + // Set positions. + // TODO: Should these be set on monitor ? data.fromX = from.getX(); data.fromY = from.getY(); data.fromZ = from.getZ(); data.toX = to.getX(); data.toY = to.getY(); data.toZ = to.getZ(); + // Cleanup. moveInfo.cleanup(); parkedInfo.add(moveInfo); } - + /** * A workaround for cancelled PlayerMoveEvents. * @@ -517,15 +529,15 @@ public class MovingListener implements Listener { data.clearMorePacketsData(); } - /** - * When a player respawns, all information related to the moving checks becomes invalid. - * - * @param event - * the event - */ - @EventHandler( - priority = EventPriority.MONITOR) - public void onPlayerRespawn(final PlayerRespawnEvent event) { + /** + * When a player respawns, all information related to the moving checks + * becomes invalid. + * + * @param event + * the event + */ + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerRespawn(final PlayerRespawnEvent event) { /* * ____ _ ____ * | _ \| | __ _ _ _ ___ _ __ | _ \ ___ ___ _ __ __ ___ ___ __ @@ -534,10 +546,15 @@ public class MovingListener implements Listener { * |_| |_|\__,_|\__, |\___|_| |_| \_\___||___/ .__/ \__,_| \_/\_/ |_| |_| * |___/ |_| */ - final MovingData data = MovingData.getData(event.getPlayer()); - data.clearFlyData(); - data.clearMorePacketsData(); - } + final Player player = event.getPlayer(); + final MovingData data = MovingData.getData(player); + data.clearFlyData(); + data.clearMorePacketsData(); + if (survivalFly.isEnabled(player)) { + data.setBack = event.getRespawnLocation(); + data.ground = event.getRespawnLocation(); + } + } /** * If a player gets teleported, it may have two reasons. Either it was NoCheat or another plugin. If it was @@ -548,8 +565,7 @@ public class MovingListener implements Listener { * @param event * the event */ - @EventHandler( - ignoreCancelled = true, priority = EventPriority.HIGHEST) + @EventHandler(ignoreCancelled = false, priority = EventPriority.HIGHEST) public void onPlayerTeleport(final PlayerTeleportEvent event) { /* * ____ _ _____ _ _ @@ -559,29 +575,34 @@ public class MovingListener implements Listener { * |_| |_|\__,_|\__, |\___|_| |_|\___|_|\___| .__/ \___/|_| \__| * |___/ |_| */ - final Player player = event.getPlayer(); - final MovingData data = MovingData.getData(player); - - final Location teleported = data.teleported; - - // If it was a teleport initialized by NoCheatPlus, do it anyway even if another plugin said "no". - final Location to = event.getTo(); - if (event.isCancelled() && teleported != null && data.teleported.equals(to)){ - // TODO: even more strict enforcing ? - event.setCancelled(false); - event.setTo(teleported); - event.setFrom(teleported); - data.clearFlyData(); - data.resetPositions(teleported); - } - else{ - // Only if it wasn't NoCheatPlus, drop data from more packets check. If it was NoCheatPlus, we don't - // want players to exploit the fly check teleporting to get rid of the "morepackets" data. - // TODO: check if to do with cancelled teleports ! - data.clearMorePacketsData(); - data.clearFlyData(); - data.resetPositions(event.isCancelled() ? event.getFrom() : to); - } + final Player player = event.getPlayer(); + final MovingData data = MovingData.getData(player); + + final Location teleported = data.teleported; + + // If it was a teleport initialized by NoCheatPlus, do it anyway even if another plugin said "no". + final Location to = event.getTo(); + if (teleported != null && teleported.equals(to)) { + // Teleport by NCP. + // Prevent cheaters getting rid of flying data (morepackets, other). + // TODO: even more strict enforcing ? + if (event.isCancelled()) { + event.setCancelled(false); + event.setTo(teleported); + event.setFrom(teleported); + } + else{ + // Not cancelled but NCP teleport. + } + // TODO: This could be done on MONITOR. + data.onSetBack(teleported); + } else { + // Only if it wasn't NoCheatPlus, drop data from more packets check. + // TODO: check if to do with cancelled teleports ! + data.clearMorePacketsData(); + data.clearFlyData(); + data.resetPositions(event.isCancelled() ? event.getFrom() : to); + } // Always drop data from fly checks, as it always loses its validity after teleports. Always! @@ -715,6 +736,22 @@ public class MovingListener implements Listener { // Entity fall-distance should be reset elsewhere. } + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerJoin(final PlayerJoinEvent event) { + final Player player = event.getPlayer(); + if (survivalFly.isEnabled(player)){ + final MovingData data = MovingData.getData(player); + // TODO: on existing set back: detect world changes and loss of world on join (+ set up some paradigm). + if (data.setBack == null){ + data.setBack = player.getLocation(); + } + if (data.fromX == Double.MAX_VALUE && data.toX == Double.MAX_VALUE){ + // TODO: re-think: more fine grained reset? + data.resetPositions(data.setBack); + } + } + } + @EventHandler(priority = EventPriority.MONITOR) public void onPlayerQuit(final PlayerQuitEvent event){ noFall.onLeave(event.getPlayer()); @@ -724,4 +761,15 @@ public class MovingListener implements Listener { public void onPlayerKick(final PlayerKickEvent event){ noFall.onLeave(event.getPlayer()); } + + /** + * Determine "some jump amplifier": 1 is jump boost, 2 is jump boost II. + * @param mcPlayer + * @return + */ + public static final double getJumpAmplifier(final EntityPlayer mcPlayer) { + if (mcPlayer.hasEffect(MobEffectList.JUMP)) { + return 1D + mcPlayer.getEffect(MobEffectList.JUMP).getAmplifier(); + } else return 0D; + } } diff --git a/src/fr/neatmonster/nocheatplus/checks/moving/Passable.java b/src/fr/neatmonster/nocheatplus/checks/moving/Passable.java index 6532f288..7761bd6c 100644 --- a/src/fr/neatmonster/nocheatplus/checks/moving/Passable.java +++ b/src/fr/neatmonster/nocheatplus/checks/moving/Passable.java @@ -18,75 +18,81 @@ public class Passable extends Check { public Passable() { super(CheckType.MOVING_PASSABLE); } - - public Location check(final Player player, final PlayerLocation from, final PlayerLocation to, final MovingData data, final MovingConfig cc){ + + public Location check(final Player player, final PlayerLocation from, final PlayerLocation to, final MovingData data, final MovingConfig cc) + { // Simple check. - if (!to.isPassable()){ - Location loc = null; // players location if should be used. + if (!to.isPassable()) { + Location loc = null; // players location if should be used. // Allow moving into the same block. - if (from.isSameBlock(to)){ - if (!from.isPassable()){ - final double eyeY = to.getY() + player.getEyeHeight(); - final int eyeBlockY = Location.locToBlock(eyeY); - if (eyeBlockY != to.getBlockY()){ - if (BlockProperties.isPassable(to.getBlockAccess(), to.getX(), eyeY, to.getZ(), to.getTypeId(to.getBlockX(), eyeBlockY, to.getBlockZ()))){ - // Allow moving inside the same block if head is free. - return null; - } - } - // Only allow moving further out of the block (still allows going round in circles :p) - // TODO: account for actual bounding box. - final Vector blockMiddle = new Vector(0.5 + from.getBlockX(), 0.5 + from.getBlockY(), 0.5 + from.getBlockZ()); - // TODO: Allow moving out of one block towards non-solid blocks (closest only ?). - // TODO: Allow moving out of half steps ? - // TODO: Allow moving towards non solid blocks. - if (blockMiddle.distanceSquared(from.getVector()) < blockMiddle.distanceSquared(to.getVector())) { - // Further check for the players location as possible set back. - loc = player.getLocation(); - if (to.isSamePos(loc) ){ - loc = null; - } - else if (!BlockProperties.isPassable(from.getBlockAccess(), loc.getX(), loc.getY(), loc.getZ(), from.getTypeId(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()))){ - // Allow the move - return null; - } - // else is passable: use the location instead of from. - } + if (from.isSameBlock(to)) { + if (!from.isPassable()) { + final double eyeY = to.getY() + player.getEyeHeight(); + final int eyeBlockY = Location.locToBlock(eyeY); + if (eyeBlockY != to.getBlockY()) { + if (BlockProperties.isPassable(to.getBlockAccess(), to.getX(), eyeY, to.getZ(), to.getTypeId(to.getBlockX(), eyeBlockY, to.getBlockZ()))) { + // Allow moving inside the same block if head is + // free. + return null; + } + } + // Only allow moving further out of the block (still allows + // going round in circles :p) + // TODO: account for actual bounding box. + final Vector blockMiddle = new Vector(0.5 + from.getBlockX(), 0.5 + from.getBlockY(), 0.5 + from.getBlockZ()); + // TODO: Allow moving out of one block towards non-solid + // blocks (closest only ?). + // TODO: Allow moving out of half steps ? + // TODO: Allow moving towards non solid blocks. + if (blockMiddle.distanceSquared(from.getVector()) < blockMiddle.distanceSquared(to.getVector())) { + // Further check for the players location as possible + // set back. + loc = player.getLocation(); + if (to.isSamePos(loc)) { + loc = null; + } else if (!BlockProperties.isPassable(from.getBlockAccess(), loc.getX(), loc.getY(), loc.getZ(), from.getTypeId(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()))) { + // Allow the move + return null; + } + // else is passable: use the location instead of from. + } } } + + // Prefer the set-back location from the data. + if (data.setBack != null && BlockProperties.isPassable(from.getBlockAccess(), data.setBack)) loc = data.setBack; + // Return the reset position. data.passableVL += 1d; final ViolationData vd = new ViolationData(this, player, data.passableVL, 1, cc.passableActions); if (vd.needsParameters()) vd.setParameter(ParameterName.BLOCK_ID, "" + to.getTypeId()); - if (executeActions(vd)){ - // TODO: Consider another set back position for this, also keeping track of players moving around in blocks. + if (executeActions(vd)) { + // TODO: Consider another set back position for this, also + // keeping track of players moving around in blocks. final Location newTo; - if (!from.isPassable() && loc == null){ - // Check if passable. - loc = player.getLocation(); - if (to.isSamePos(loc) || !BlockProperties.isPassable(from.getBlockAccess(), loc.getX(), loc.getY(), loc.getZ(), from.getTypeId(from.getBlockX(), from.getBlockY(), from.getBlockZ()))){ - loc = null; - } + if (loc == null && !from.isPassable()) { + // Check if passable. + loc = player.getLocation(); + if (to.isSamePos(loc) || !BlockProperties.isPassable(from.getBlockAccess(), loc.getX(), loc.getY(), loc.getZ(), from.getTypeId(from.getBlockX(), from.getBlockY(), from.getBlockZ()))) { + loc = null; + } } if (loc != null) newTo = loc; else newTo = from.getLocation(); newTo.setYaw(to.getYaw()); newTo.setPitch(to.getPitch()); return newTo; - } - } - else{ + } + } else { data.passableVL *= 0.99; } return null; } @Override - protected Map getParameterMap(final ViolationData violationData) { - // TODO Auto-generated method stub + protected Map getParameterMap(final ViolationData violationData) + { return super.getParameterMap(violationData); } - - } diff --git a/src/fr/neatmonster/nocheatplus/checks/moving/SurvivalFly.java b/src/fr/neatmonster/nocheatplus/checks/moving/SurvivalFly.java index c2d31c5f..7dcd4c31 100644 --- a/src/fr/neatmonster/nocheatplus/checks/moving/SurvivalFly.java +++ b/src/fr/neatmonster/nocheatplus/checks/moving/SurvivalFly.java @@ -1,5 +1,6 @@ package fr.neatmonster.nocheatplus.checks.moving; +import java.util.ArrayList; import java.util.Locale; import net.minecraft.server.EntityPlayer; @@ -13,7 +14,9 @@ import fr.neatmonster.nocheatplus.checks.Check; import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.ViolationData; import fr.neatmonster.nocheatplus.players.Permissions; +import fr.neatmonster.nocheatplus.utilities.ActionFrequency; import fr.neatmonster.nocheatplus.utilities.BlockProperties; +import fr.neatmonster.nocheatplus.utilities.CheckUtils; import fr.neatmonster.nocheatplus.utilities.PlayerLocation; /* @@ -47,6 +50,9 @@ public class SurvivalFly extends Check { /** Faster moving down stream (water mainly). */ public static final double modDownStream = 0.19 / swimmingSpeed; + /** To join some tags with moving check violations. */ + private final ArrayList tags = new ArrayList(15); + /** * Instantiates a new survival fly check. */ @@ -91,7 +97,7 @@ public class SurvivalFly extends Check { */ public Location check(final Player player, final EntityPlayer mcPlayer, final PlayerLocation from, final PlayerLocation to, final MovingData data, final MovingConfig cc) { final long now = System.currentTimeMillis(); - + tags.clear(); // A player is considered sprinting if the flag is set and if he has enough food level. final boolean sprinting = player.isSprinting() && player.getFoodLevel() > 5; @@ -108,55 +114,61 @@ public class SurvivalFly extends Check { // If we don't have any setBack, choose the location the player comes from. if (data.setBack == null) data.setBack = from.getLocation(); - - boolean resetFrom = fromOnGround || from.isInLiquid() || from.isOnLadder() || from.isInWeb(); - - final double setBackYDistance = to.getY() - data.setBack.getY(); - // If the player has touched the ground but it hasn't been noticed by the plugin, the workaround is here. - if (!resetFrom){ - // Don't set "useWorkaround = x()", to avoid potential trouble with reordering to come, and similar. - boolean useWorkaround = false; - // Check for moving off stairs. - if (!useWorkaround && from.isAboveStairs()) useWorkaround = true; - // Check for "lost touch", for when moving events were not created, for instance (1/256). - if (!useWorkaround){ - final boolean inconsistent = yDistance > 0 && yDistance < 0.5 && data.survivalFlyLastYDist < 0 - && setBackYDistance > 0D && setBackYDistance <= 1.5D; - if (inconsistent){ - if (cc.debug) System.out.println(player.getName() + " Y-INCONSISTENCY"); - if (data.fromX != Double.MAX_VALUE){ - // Interpolate from last to-coordinates to the from coordinates (with some safe-guard). - final double dX = from.getX() - data.fromX; - final double dY = from.getY() - data.fromY; - final double dZ = from.getZ() - data.fromZ; - if (dX * dX + dY * dY + dZ * dZ < 0.5){ // TODO: adjust limit maybe. - // Check full bounding box since last from. - if (cc.debug) System.out.println(player.getName() + " CONSIDER WORKAROUND"); - final double minY = Math.min(data.toY, Math.min(data.fromY, from.getY())); - final double iY = minY; // TODO ... - final double r = from.getWidth() / 2.0; - if (BlockProperties.isOnGround(from.getBlockAccess(), Math.min(data.fromX, from.getX()) - r, iY - cc.yOnGround, Math.min(data.fromZ, from.getZ()) - r, Math.max(data.fromX, from.getX()) + r, iY + 0.25, Math.max(data.fromZ, from.getZ()) + r)) useWorkaround = true; - } - } - } - } - if (useWorkaround){ // !toOnGround && to.isAboveStairs()) { - // Set the new setBack and reset the jumpPhase. - - // Maybe don't adapt the setback (unless null)! -// data.setBack = from.getLocation(); - data.setBack.setY(Location.locToBlock(data.setBack.getY())); - // data.ground ? - // ? set jumpphase to height / 0.15 ? - data.survivalFlyJumpPhase = 0; - data.jumpAmplifier = 0; // Might conflict, should probably fetch. - data.clearAccounting(); - // Tell NoFall that we assume the player to have been on ground somehow. - data.noFallAssumeGround = true; - resetFrom = true; - if (cc.debug) System.out.println(player.getName() + " Y-INCONSISTENCY WORKAROUND USED"); - } - } + + boolean resetFrom = fromOnGround || from.isInLiquid() || from.isOnLadder() || from.isInWeb(); + + final double setBackYDistance = to.getY() - data.setBack.getY(); + // If the player has touched the ground but it hasn't been noticed by + // the plugin, the workaround is here. + if (!resetFrom) { + // Don't set "useWorkaround = x()", to avoid potential trouble with + // reordering to come, and similar. + boolean useWorkaround = false; + boolean setBackSafe = false; // Let compiler remove this if necessary. + // Check for moving off stairs. + if (!useWorkaround && from.isAboveStairs()) { + useWorkaround = true; + setBackSafe = true; + } + // Check for "lost touch", for when moving events were not created, + // for instance (1/256). + if (!useWorkaround && yDistance > 0 && yDistance < 0.5 && data.survivalFlyLastYDist < 0 + && setBackYDistance > 0D && setBackYDistance <= 1.5D) { + if (data.fromX != Double.MAX_VALUE) { + // Interpolate from last to-coordinates to the from + // coordinates (with some safe-guard). + final double dX = from.getX() - data.fromX; + final double dY = from.getY() - data.fromY; + final double dZ = from.getZ() - data.fromZ; + if (dX * dX + dY * dY + dZ * dZ < 0.5) { // TODO: adjust + // limit maybe. + // Check full bounding box since last from. + final double minY = Math.min(data.toY, Math.min(data.fromY, from.getY())); + final double iY = minY; // TODO ... + final double r = from.getWidth() / 2.0; + if (BlockProperties.isOnGround(from.getBlockAccess(), Math.min(data.fromX, from.getX()) - r, iY - cc.yOnGround, Math.min(data.fromZ, from.getZ()) - r, Math.max(data.fromX, from.getX()) + r, iY + 0.25, Math.max(data.fromZ, from.getZ()) + r)) { + useWorkaround = true; + setBackSafe = true; + } + } + } + } + if (useWorkaround) { // !toOnGround && to.isAboveStairs()) { + // Set the new setBack and reset the jumpPhase. + if (setBackSafe) data.setBack = from.getLocation(); + // TODO: This seems dubious ! + data.setBack.setY(Location.locToBlock(data.setBack.getY())); + // data.ground ? + // ? set jumpphase to height / 0.15 ? + data.survivalFlyJumpPhase = 0; + data.jumpAmplifier = MovingListener.getJumpAmplifier(mcPlayer); + data.clearAccounting(); + // Tell NoFall that we assume the player to have been on ground somehow. + data.noFallAssumeGround = true; + resetFrom = true; // Note: if removing this, other conditions need to check noFallAssume... + tags.add("lostground"); + } + } // Player on ice? Give him higher max speed. if (from.isOnIce() || to.isOnIce()) @@ -190,16 +202,14 @@ public class SurvivalFly extends Check { hAllowedDistance *= cc.survivalFlySpeedingSpeed/ 100D; } - // TODO: Optimize: maybe only do the permission checks and modifiers if the distance is too big. - // (Depending on permission plugin, with pex it will be hardly 1000 ns for all moving perms, if all false.) + // TODO: More after-failure checks, to prevent unnecessary permission checking etc. + // TODO: Split off not frequently used parts to methods. // If the player is on ice, give him an higher maximum speed. if (data.survivalFlyOnIce > 0) hAllowedDistance *= modIce; - // Taken directly from Minecraft code, should work. - -// player.hasPotionEffect(PotionEffectType.SPEED); + // Speed amplifier. if (mcPlayer.hasEffect(MobEffectList.FASTER_MOVEMENT)) hAllowedDistance *= 1.0D + 0.2D * (mcPlayer.getEffect(MobEffectList.FASTER_MOVEMENT).getAmplifier() + 1); @@ -211,12 +221,15 @@ public class SurvivalFly extends Check { // Judge if horizontal speed is above limit. double hDistanceAboveLimit = hDistance - hAllowedDistance - data.horizontalFreedom; - // Prevent players from walking on a liquid. - // TODO: yDistance == 0D <- should there not be a tolerance +- or 0...x ? - if (hDistanceAboveLimit <= 0D && hDistance > 0.1D && yDistance == 0D - && BlockProperties.isLiquid(to.getTypeId()) && !toOnGround - && to.getY() % 1D < 0.8D) - hDistanceAboveLimit = hDistance; + // Tag for simple speed violation (medium), might get overridden. + if (hDistanceAboveLimit > 0) tags.add("hspeed"); + + // Prevent players from walking on a liquid. + // TODO: yDistance == 0D <- should there not be a tolerance +- or 0...x ? + if (hDistanceAboveLimit <= 0D && hDistance > 0.1D && yDistance == 0D && BlockProperties.isLiquid(to.getTypeId()) && !toOnGround && to.getY() % 1D < 0.8D) { + hDistanceAboveLimit = Math.max(hDistanceAboveLimit, hDistance); + tags.add("waterwalk"); + } // Prevent players from sprinting if they're moving backwards. if (hDistanceAboveLimit <= 0D && sprinting) { @@ -225,7 +238,10 @@ public class SurvivalFly extends Check { && yaw > 270F && yaw < 360F || xDistance > 0D && zDistance < 0D && yaw > 0F && yaw < 90F || xDistance > 0D && zDistance > 0D && yaw > 90F && yaw < 180F){ // Assumes permission check to be the heaviest (might be mistaken). - if (!player.hasPermission(Permissions.MOVING_SURVIVALFLY_SPRINTING)) hDistanceAboveLimit = hDistance; + if (!player.hasPermission(Permissions.MOVING_SURVIVALFLY_SPRINTING)){ + hDistanceAboveLimit = Math.max(hDistanceAboveLimit, hDistance); + tags.add("sprintback"); + } } } @@ -233,10 +249,11 @@ public class SurvivalFly extends Check { // Did he go too far? if (hDistanceAboveLimit > 0 && sprinting) - // Try to treat it as a the "bunnyhop" problem. + // Try to treat it as a the "bunny-hop" problem. if (data.bunnyhopDelay <= 0 && hDistanceAboveLimit > 0.05D && hDistanceAboveLimit < 0.28D) { data.bunnyhopDelay = 9; hDistanceAboveLimit = 0D; + tags.add("bunny"); // TODO: Which here... } if (hDistanceAboveLimit > 0D) { @@ -244,9 +261,13 @@ public class SurvivalFly extends Check { hDistanceAboveLimit -= data.horizontalBuffer; data.horizontalBuffer = 0D; - // Put back the "overconsumed" buffer. - if (hDistanceAboveLimit < 0D) - data.horizontalBuffer = -hDistanceAboveLimit; + // Put back the "over-consumed" buffer. + if (hDistanceAboveLimit < 0D){ + data.horizontalBuffer = -hDistanceAboveLimit; + } + if (hDistanceAboveLimit <= 0){ + tags.add("hbuffer"); // TODO: ... + } } else data.horizontalBuffer = Math.min(1D, data.horizontalBuffer - hDistanceAboveLimit); @@ -255,27 +276,22 @@ public class SurvivalFly extends Check { if (from.isInWeb()){ // Very simple: force players to descend or stay. vAllowedDistance = from.isOnGround() ? 0.1D : 0; - data.jumpAmplifier = 0; + data.jumpAmplifier = 0; // TODO: later maybe fetch. vDistanceAboveLimit = yDistance; if (cc.survivalFlyCobwebHack && vDistanceAboveLimit > 0 && hDistanceAboveLimit <= 0){ - if (now - data.survivalFlyCobwebTime > 3000){ - data.survivalFlyCobwebTime = now; - data.survivalFlyCobwebVL = vDistanceAboveLimit * 100D; - } - else data.survivalFlyCobwebVL += vDistanceAboveLimit * 100D; - if (data.survivalFlyCobwebVL < 550) { // Totally random ! - if (cc.debug) System.out.println(player.getName()+ " (Cobweb: silent set-back-)"); - // Silently set back. - if (data.setBack == null) data.setBack = player.getLocation(); - data.survivalFlyJumpPhase = 0; - data.setBack.setYaw(to.getYaw()); - data.setBack.setPitch(to.getPitch()); - data.survivalFlyLastYDist = Double.MAX_VALUE; - return data.setBack; + // TODO: This seemed fixed by latest builds of CraftBukkit, test and remove if appropriate! + final Location silentSetBack = hackCobweb(player, data, to, now, vDistanceAboveLimit); + if (silentSetBack != null){ + if (cc.debug) System.out.println(player.getName()+ " (Cobweb: silent set-back)"); + return silentSetBack; } } + if (vDistanceAboveLimit > 0) tags.add("vweb"); } + // else if (verticalFreedom <= 0.001 && from.isOnLadder) .... + // else if (verticalFreedom <= 0.001 (?) & from.isInFluid else{ + // Check traveled y-distance, orientation is air + jumping + velocity (as far as it gets). vAllowedDistance = (!(fromOnGround || data.noFallAssumeGround) && !toOnGround ? 1.45D : 1.35D) + data.verticalFreedom; final int maxJumpPhase; if (data.jumpAmplifier > 0){ @@ -287,46 +303,44 @@ public class SurvivalFly extends Check { vAllowedDistance -= Math.max(0, (data.survivalFlyJumpPhase - maxJumpPhase) * 0.15D); } - vDistanceAboveLimit = to.getY() - data.setBack.getY() - vAllowedDistance; - -// System.out.println("vda = " +vDistanceAboveLimit + " / vc = " + data.verticalVelocityCounter + " / vf = " + data.verticalFreedom + " / v = " + player.getVelocity().length()); + vDistanceAboveLimit = to.getY() - data.setBack.getY() - vAllowedDistance; - // Step can also be blocked. - if ((fromOnGround || data.noFallAssumeGround) && toOnGround && Math.abs(yDistance - 1D) <= cc.yStep && vDistanceAboveLimit <= 0D - && !player.hasPermission(Permissions.MOVING_SURVIVALFLY_STEP)) - vDistanceAboveLimit = Math.max(vDistanceAboveLimit, Math.abs(yDistance)); + // Simple-step blocker. + if ((fromOnGround || data.noFallAssumeGround) && toOnGround && Math.abs(yDistance - 1D) <= cc.yStep && vDistanceAboveLimit <= 0D && !player.hasPermission(Permissions.MOVING_SURVIVALFLY_STEP)) { + vDistanceAboveLimit = Math.max(vDistanceAboveLimit, Math.abs(yDistance)); + tags.add("step"); + } + } + + if (data.noFallAssumeGround || fromOnGround || toOnGround) { + // Some reset condition. + data.jumpAmplifier = MovingListener.getJumpAmplifier(mcPlayer); + } + + final boolean resetTo = toOnGround || to.isInLiquid() || to.isOnLadder()|| to.isInWeb(); + + // Accounting support. + if (cc.survivalFlyAccounting && !resetFrom && !resetTo) { + // Currently only for "air" phases. + // Horizontal. + if (data.horizontalFreedom <= 0.001D){ + // This only checks general speed decrease oncevelocity is smoked up. + hDistanceAboveLimit = Math.max(hDistanceAboveLimit, doAccounting(now, hDistance, data.hDistSum, data.hDistCount, tags, "hacc")); + } + // Vertical. + if (data.verticalFreedom <= 0.001D) { // && ! resetTo) { + // Here yDistance can be negative and positive (!). + // TODO: Might demand resetting on some direction changes (bunny,) + vDistanceAboveLimit = Math.max(vDistanceAboveLimit, doAccounting(now, yDistance, data.vDistSum, data.vDistCount, tags, "vacc")); +// // Check if y-direction is going upwards without speed / ground. +// if (yDistance >= 0 && data.survivalFlyLastYDist < 0 && !data.toWasReset) { +// // Moving upwards without having touched the ground. +// vDistanceAboveLimit = Math.max(vDistanceAboveLimit, Math.abs(yDistance)); +// tags.add("ychange"); +// } + } + } - } - if (data.noFallAssumeGround || fromOnGround || toOnGround) - data.jumpAmplifier = 0D; - - if (cc.survivalFlyAccounting && !resetFrom){ - final boolean useH = data.horizontalFreedom <= 0.001D; - final boolean useV = data.verticalFreedom <= 0.001D; - if (useH){ - data.hDistSum.add(now, (float) hDistance); - data.hDistCount.add(now, 1f); - } - if (useV){ - data.vDistSum.add(now, (float) (yDistance)); - data.vDistCount.add(now, 1f); - } - if (useH && data.hDistCount.getScore(2) > 0 && data.hDistCount.getScore(1) > 0){ - final float hsc0 = data.hDistSum.getScore(1); - final float hsc1 = data.hDistSum.getScore(2); - if (hsc0 < hsc1 || hDistance < 3.9 && hsc0 == hsc1){ - hDistanceAboveLimit = Math.max(hDistanceAboveLimit, hsc0 - hsc1); - } - } - if (useV && data.vDistCount.getScore(2) > 0 && data.vDistCount.getScore(1) > 0){ - final float vsc0 = data.vDistSum.getScore(1); - final float vsc1 = data.vDistSum.getScore(2); - if (vsc0 < vsc1 || yDistance < 3.9 && vsc0 == vsc1){ - vDistanceAboveLimit = Math.max(vDistanceAboveLimit, vsc0 - vsc1); - } - } - } - final double result = (Math.max(hDistanceAboveLimit, 0D) + Math.max(vDistanceAboveLimit, 0D)) * 100D; data.survivalFlyJumpPhase++; @@ -336,11 +350,11 @@ public class SurvivalFly extends Check { System.out.println(player.getName() + " hDist: " + hDistance + " / " + hAllowedDistance + " , vDist: " + (yDistance) + " ("+player.getVelocity().getY()+")" + " / " + vAllowedDistance); System.out.println(player.getName() + " y" + (fromOnGround?"(onground)":"") + (data.noFallAssumeGround?"(assumeonground)":"") + ": " + from.getY() +"(" + player.getLocation().getY() + ") -> " + to.getY()+ (toOnGround?"(onground)":"")); if (cc.survivalFlyAccounting) System.out.println(player.getName() + " h=" + data.hDistSum.getScore(1f)+"/" + data.hDistSum.getScore(1) + " , v=" + data.vDistSum.getScore(1f)+"/"+data.vDistSum.getScore(1) ); + System.out.println(player.getName() + " tags: " + CheckUtils.join(tags, "+")); } // Did the player move in unexpected ways?// Did the player move in unexpected ways? if (result > 0D) { -// System.out.println(BlockProperties.isStairs(from.getTypeIdBelow()) + " / " + BlockProperties.isStairs(to.getTypeIdBelow())); // Increment violation counter. data.survivalFlyVL += result; data.clearAccounting(); @@ -352,10 +366,12 @@ public class SurvivalFly extends Check { vd.setParameter(ParameterName.LOCATION_FROM, String.format(Locale.US, "%.2f, %.2f, %.2f", from.getX(), from.getY(), from.getZ())); vd.setParameter(ParameterName.LOCATION_TO, String.format(Locale.US, "%.2f, %.2f, %.2f", to.getX(), to.getY(), to.getZ())); vd.setParameter(ParameterName.DISTANCE, String.format(Locale.US, "%.2f", to.getLocation().distance(from.getLocation()))); + vd.setParameter(ParameterName.TAGS, CheckUtils.join(tags, "+")); // Always set. } data.survivalFlyVLTime = now; if (executeActions(vd)){ data.survivalFlyLastYDist = Double.MAX_VALUE; + data.toWasReset = false; // Compose a new location based on coordinates of "newTo" and viewing direction of "event.getTo()" to // allow the player to look somewhere else despite getting pulled back by NoCheatPlus. return new Location(player.getWorld(), data.setBack.getX(), data.setBack.getY(), data.setBack.getZ(), @@ -368,7 +384,7 @@ public class SurvivalFly extends Check { } // Violation or not, apply reset conditions (cancel would have returned above). - final boolean resetTo = toOnGround || to.isInLiquid() || to.isOnLadder()|| to.isInWeb(); + data.toWasReset = resetTo || data.noFallAssumeGround; if (resetTo){ // The player has moved onto ground. data.setBack = to.getLocation(); @@ -384,5 +400,59 @@ public class SurvivalFly extends Check { data.survivalFlyLastYDist = yDistance; return null; } - + + /** + * Allow accumulating some vls and silently set the player back. + * + * @param player + * @param data + * @param cc + * @param to + * @param now + * @param vDistanceAboveLimit + * @return + */ + private final Location hackCobweb(final Player player, final MovingData data, final PlayerLocation to, + final long now, final double vDistanceAboveLimit) + { + if (now - data.survivalFlyCobwebTime > 3000) { + data.survivalFlyCobwebTime = now; + data.survivalFlyCobwebVL = vDistanceAboveLimit * 100D; + } else data.survivalFlyCobwebVL += vDistanceAboveLimit * 100D; + if (data.survivalFlyCobwebVL < 550) { // Totally random ! + // Silently set back. + if (data.setBack == null) data.setBack = player.getLocation(); + data.survivalFlyJumpPhase = 0; + data.setBack.setYaw(to.getYaw()); + data.setBack.setPitch(to.getPitch()); + data.survivalFlyLastYDist = Double.MAX_VALUE; + return data.setBack; + } else return null; + } + + /** + * Keep track of values, demanding that with time the values decrease.
+ * The ActionFrequency objects have 3 buckets. + * @param now + * @param value + * @param sum + * @param count + * @param tags + * @param tag + * @return + */ + private static final double doAccounting(final long now, final double value, final ActionFrequency sum, final ActionFrequency count, final ArrayList tags, String tag) + { + sum.add(now, (float) value); + count.add(now, 1f); + if (count.getScore(2) > 0 && count.getScore(1) > 0) { + final float sc0 = sum.getScore(1); + final float sc1 = sum.getScore(2); + if (sc0 < sc1 || value < 3.9 && sc0 == sc1) { + tags.add(tag); + return sc0 - sc1; + } + } + return 0; + } } diff --git a/src/fr/neatmonster/nocheatplus/command/admin/RemovePlayerCommand.java b/src/fr/neatmonster/nocheatplus/command/admin/RemovePlayerCommand.java index 9142cdf6..74ddc234 100644 --- a/src/fr/neatmonster/nocheatplus/command/admin/RemovePlayerCommand.java +++ b/src/fr/neatmonster/nocheatplus/command/admin/RemovePlayerCommand.java @@ -61,7 +61,7 @@ public class RemovePlayerCommand extends NCPCommand { if (DataManager.removeExecutionHistory(checkType, playerName)) histRemoved = true; - final boolean dataRemoved = CheckType.removeData(playerName, checkType) || DataManager.clearComponentData(checkType, playerName); + final boolean dataRemoved = DataManager.removeData(playerName, checkType); if (dataRemoved || histRemoved){ String which; diff --git a/src/fr/neatmonster/nocheatplus/components/IRemoveData.java b/src/fr/neatmonster/nocheatplus/components/IRemoveData.java index 839b5e79..02440f05 100644 --- a/src/fr/neatmonster/nocheatplus/components/IRemoveData.java +++ b/src/fr/neatmonster/nocheatplus/components/IRemoveData.java @@ -1,7 +1,8 @@ package fr.neatmonster.nocheatplus.components; /** - * Interface for component registration to allow cleanup for player data. + * Interface for component registration to allow cleanup for player data.
+ * NOTE: For CheckType-specific data removal, IHaveCheckType should be implemented, otherwise this data might get ignored until plugin-disable. * @author mc_dev * */ diff --git a/src/fr/neatmonster/nocheatplus/hooks/NCPExemptionManager.java b/src/fr/neatmonster/nocheatplus/hooks/NCPExemptionManager.java index ef9d41c4..75ea2fdc 100644 --- a/src/fr/neatmonster/nocheatplus/hooks/NCPExemptionManager.java +++ b/src/fr/neatmonster/nocheatplus/hooks/NCPExemptionManager.java @@ -111,37 +111,34 @@ public class NCPExemptionManager { exemptPermanently(player.getEntityId(), checkType); } - /** - * This should be registered before all other listeners of NoCheatPlus. - * - * NOTE: For internal use only, DO NOT CALL FROM OUTSIDE. - * - * @return the listener - */ - public static Listener getListener() { - return new Listener() { - @SuppressWarnings("unused") - @EventHandler( - ignoreCancelled = true, priority = EventPriority.LOWEST) - public void onPlayerJoin(final PlayerJoinEvent event) { - NCPExemptionManager.registerPlayer(event.getPlayer()); - } + /** + * This should be registered before all other listeners of NoCheatPlus. + * + * NOTE: For internal use only, DO NOT CALL FROM OUTSIDE. + * + * @return the listener + */ + public static Listener getListener() { + return new Listener() { + @SuppressWarnings("unused") + @EventHandler(priority = EventPriority.LOWEST) + public void onPlayerJoin(final PlayerJoinEvent event) { + NCPExemptionManager.registerPlayer(event.getPlayer()); + } - @SuppressWarnings("unused") - @EventHandler( - ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onPlayerQuit(final PlayerQuitEvent event) { - NCPExemptionManager.tryToRemove(event.getPlayer()); - } - - @SuppressWarnings("unused") - @EventHandler( - ignoreCancelled = true, priority = EventPriority.MONITOR) - public void onPlayerKick(final PlayerKickEvent event) { - NCPExemptionManager.tryToRemove(event.getPlayer()); - } - }; - } + @SuppressWarnings("unused") + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(final PlayerQuitEvent event) { + NCPExemptionManager.tryToRemove(event.getPlayer()); + } + + @SuppressWarnings("unused") + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerKick(final PlayerKickEvent event) { + NCPExemptionManager.tryToRemove(event.getPlayer()); + } + }; + } /** * Check if an entity is exempted from a check right now by entity id. diff --git a/src/fr/neatmonster/nocheatplus/permissions/PermissionUtil.java b/src/fr/neatmonster/nocheatplus/permissions/PermissionUtil.java new file mode 100644 index 00000000..bb655d0f --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/permissions/PermissionUtil.java @@ -0,0 +1,67 @@ +package fr.neatmonster.nocheatplus.permissions; + +import java.util.Set; + +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.PluginManager; + +public class PermissionUtil { + + public static SimpleCommandMap getCommandMap(){ + return (((CraftServer) Bukkit.getServer()).getCommandMap()); + } + + /** + * TODO: Return undo info. + * @param permissionBase + * @param ignoredCommands + * @param ops + */ + public static void alterCommandPermissions(String permissionBase, Set ignoredCommands, boolean invertIgnored, boolean ops){ + PluginManager pm = Bukkit.getPluginManager(); + Permission rootPerm = pm.getPermission(permissionBase); + if (rootPerm == null){ + rootPerm = new Permission(permissionBase); + pm.addPermission(rootPerm); + } + SimpleCommandMap map = getCommandMap(); + for (Command command : map.getCommands()){ + String lcLabel = command.getLabel().trim().toLowerCase(); + if (ignoredCommands != null){ + if (ignoredCommands.contains(lcLabel)){ + if (!invertIgnored) continue; + } + else if (invertIgnored) continue; + } + // Set the permission for the command. + String cmdPermName = command.getPermission(); + boolean cmdHadPerm; + if (cmdPermName == null){ + // Set a permission. + cmdPermName = permissionBase + "." + lcLabel; + command.setPermission(cmdPermName); + cmdHadPerm = false; + } + else cmdHadPerm = true; + // Set permission default behavior. + Permission cmdPerm = pm.getPermission(cmdPermName); + if (cmdPerm == null){ + if (!cmdHadPerm){ + cmdPerm = new Permission(cmdPermName); + cmdPerm.addParent(rootPerm, true); + cmdPerm.setDefault(ops ? PermissionDefault.OP : PermissionDefault.FALSE); + pm.addPermission(cmdPerm); + } + } + else{ + // Change default of the permission. + cmdPerm.setDefault(ops ? PermissionDefault.OP : PermissionDefault.FALSE); + } + } + } +} diff --git a/src/fr/neatmonster/nocheatplus/players/DataManager.java b/src/fr/neatmonster/nocheatplus/players/DataManager.java index 1011c057..a5539dfb 100644 --- a/src/fr/neatmonster/nocheatplus/players/DataManager.java +++ b/src/fr/neatmonster/nocheatplus/players/DataManager.java @@ -40,6 +40,14 @@ import fr.neatmonster.nocheatplus.config.ConfigFile; import fr.neatmonster.nocheatplus.config.ConfigManager; import fr.neatmonster.nocheatplus.hooks.APIUtils; +/** + * Central access point for a lot of functionality for managing data, especially removing data for cleanup.
+ * Originally intended as temporary or intermediate design, this might help reorganizing the API at some point.
+ * However i could not yet find a pleasing way for generic configuration access for a centralized data management (all in one), + * so this might just be a workarounds class for coping with the current design, until somehow resolved in another way. + * @author mc_dev + * + */ public class DataManager implements Listener, INotifyReload, INeedConfig, IComponentRegistry{ protected static DataManager instance = null; @@ -65,15 +73,28 @@ public class DataManager implements Listener, INotifyReload, INeedConfig, ICompo */ protected final Map> executionHistories = new HashMap>(); + /** + * Duration in milliseconds for expiration of logged off players data. + * Disabled with 0, in the config minutes are used as unit. + */ protected long durExpireData = 0; + /** Data and execution history. */ protected boolean deleteData = true; + /** Violation history and execution history. */ protected boolean deleteHistory = false; + /** + * Sets the static instance reference. + */ public DataManager(){ instance = this; } + /** + * Check the logged out players for if any data can be removed.
+ * Currently only "dumb" full removal is performed. Later it is thinkable to remove "as much as reasonable". + */ public void checkExpiration(){ if (durExpireData <= 0) return; final long now = System.currentTimeMillis(); @@ -119,6 +140,10 @@ public class DataManager implements Listener, INotifyReload, INeedConfig, ICompo onLeave(event.getPlayer()); } + /** + * Quit or kick. + * @param player + */ private final void onLeave(final Player player) { final long now = System.currentTimeMillis(); lastLogout.put(player.getName(), now); @@ -127,10 +152,13 @@ public class DataManager implements Listener, INotifyReload, INeedConfig, ICompo @Override public void onReload() { - // future + // present. adjustSettings(); } + /** + * Fetch settings from the current default config. + */ private void adjustSettings() { final ConfigFile config = ConfigManager.getConfigFile(); durExpireData = config.getLong(ConfPaths.DATA_EXPIRATION_DURATION) * 60000L; // in minutes @@ -138,6 +166,12 @@ public class DataManager implements Listener, INotifyReload, INeedConfig, ICompo deleteHistory = config.getBoolean(ConfPaths.DATA_EXPIRATION_HISTORY); } + /** + * Used by checks to register the history for external access.
+ * NOTE: This method is not really meant ot be used from outside NCP. + * @param type + * @param histories + */ public static void registerExecutionHistory(CheckType type, Map histories) { instance.executionHistories.put(type, histories); } @@ -154,6 +188,12 @@ public class DataManager implements Listener, INotifyReload, INeedConfig, ICompo return null; } + /** + * Remove the execution history for a player for the given check type. + * @param type + * @param playerName + * @return + */ public static boolean removeExecutionHistory(final CheckType type, final String playerName){ boolean removed = false; // TODO: design ... @@ -197,9 +237,36 @@ public class DataManager implements Listener, INotifyReload, INeedConfig, ICompo ViolationHistory.clear(checkType); } + /** + * Remove the player data for a given player and a given check type. CheckType.ALL and null will be interpreted as removing all data.
+ * @param playerName Exact player name. + * @param checkType Check type to remove data for, null is regarded as ALL. + * @return If any data was present. + */ + public static boolean removeData(final String playerName, CheckType checkType) { + if (checkType == null) checkType = CheckType.ALL; + boolean had = false; + + // Check extended registered components. + if (clearComponentData(checkType, playerName)) had = true; + + // Collect factories. + final Set factories = new HashSet(); + for (CheckType otherType : APIUtils.getWithChildren(checkType)){ + final CheckDataFactory otherFactory = otherType.getDataFactory(); + if (otherFactory != null) factories.add(otherFactory); + } + // Remove data. + for (final CheckDataFactory otherFactory : factories){ + if (otherFactory.removeData(playerName) != null) had = true; + } + + return had; + } + /** * Clear player related data, only for registered components (not execution history, violation history, normal check data).
- * That should at least go for chat engione data. + * That should at least go for chat engine data. * @param CheckType * @param PlayerName * @return If something was removed. @@ -249,7 +316,7 @@ public class DataManager implements Listener, INotifyReload, INeedConfig, ICompo } /** - * Cleanup method. + * Cleanup method, removes all data and config, but does not call ConfigManager.cleanup. */ public void onDisable() { clearData(CheckType.ALL); diff --git a/src/fr/neatmonster/nocheatplus/players/Permissions.java b/src/fr/neatmonster/nocheatplus/players/Permissions.java index c0d94c52..506b1820 100644 --- a/src/fr/neatmonster/nocheatplus/players/Permissions.java +++ b/src/fr/neatmonster/nocheatplus/players/Permissions.java @@ -52,7 +52,7 @@ public class Permissions { // Permissions for the individual checks. - private static final String CHECKS = NOCHEATPLUS + ".checks"; + public static final String CHECKS = NOCHEATPLUS + ".checks"; /* * 888 88b, 888 888 888 88b, 888 @@ -61,7 +61,7 @@ public class Permissions { * 888 88b, 888 Y888 888P Y888 , 888 b 888 88b, 888 888 , ,ee 888 888 b * 888 88P' 888 "88 88" "88,e8' 888 8b 888 88P' 888 "YeeP" "88 888 888 8b */ - private static final String BLOCKBREAK = CHECKS + ".blockbreak"; + public static final String BLOCKBREAK = CHECKS + ".blockbreak"; public static final String BLOCKBREAK_BREAK = BLOCKBREAK + ".break"; public static final String BLOCKBREAK_BREAK_LIQUID = BLOCKBREAK_BREAK + ".liquid"; public static final String BLOCKBREAK_DIRECTION = BLOCKBREAK + ".direction"; @@ -78,7 +78,7 @@ public class Permissions { * 888 88b, 888 Y888 888P Y888 , 888 b 888 888 888 888 888 , 888 ,ee 888 Y888 , 888 * 888 88P' 888 "88 88" "88,e8' 888 8b 888 888 888 888 "YeeP" 888 "88 888 "88,e8' 888 */ - private static final String BLOCKINTERACT = CHECKS + ".blockinteract"; + public static final String BLOCKINTERACT = CHECKS + ".blockinteract"; public static final String BLOCKINTERACT_DIRECTION = BLOCKINTERACT + ".direction"; public static final String BLOCKINTERACT_REACH = BLOCKINTERACT + ".reach"; @@ -89,9 +89,9 @@ public class Permissions { * 888 88b, 888 Y888 888P Y888 , 888 b 888 888 ,ee 888 Y888 , 888 , * 888 88P' 888 "88 88" "88,e8' 888 8b 888 888 "88 888 "88,e8' "YeeP" */ - private static final String BLOCKPLACE = CHECKS + ".blockplace"; + public static final String BLOCKPLACE = CHECKS + ".blockplace"; public static final String BLOCKPLACE_AGAINST = BLOCKPLACE + ".against"; - public static final String BLOCKPLACE_AGAINST_AIR = BLOCKPLACE_AGAINST + ".air"; + public static final String BLOCKPLACE_AGAINST_AIR = BLOCKPLACE_AGAINST + ".air"; public static final String BLOCKPLACE_AGAINST_LIQUIDS = BLOCKPLACE_AGAINST + ".liquids"; public static final String BLOCKPLACE_DIRECTION = BLOCKPLACE + ".direction"; public static final String BLOCKPLACE_FASTPLACE = BLOCKPLACE + ".fastplace"; @@ -106,19 +106,19 @@ public class Permissions { * Y888 ,d 888 888 ,ee 888 888 * "88,d88 888 888 "88 888 888 */ - private static final String CHAT = CHECKS + ".chat"; + public static final String CHAT = CHECKS + ".chat"; public static final String CHAT_CAPTCHA = CHAT + ".captcha"; public static final String CHAT_COLOR = CHAT + ".color"; public static final String CHAT_COMMANDS = CHAT + ".commands"; - public static final String CHAT_TEXT = CHAT + ".text"; + public static final String CHAT_TEXT = CHAT + ".text"; public static final String CHAT_LOGINS = CHAT + ".logins"; public static final String CHAT_RELOG = CHAT + ".relog"; /* * Combined ! */ - private static final String COMBINED = CHECKS + ".combined"; - public static final String COMBINED_IMPROBABLE = COMBINED + ".improbable"; + public static final String COMBINED = CHECKS + ".combined"; + public static final String COMBINED_IMPROBABLE = COMBINED + ".improbable"; /* * 888'Y88 ,e, 888 d8 @@ -129,7 +129,7 @@ public class Permissions { * , 88P * "8",P" */ - private static final String FIGHT = CHECKS + ".fight"; + public static final String FIGHT = CHECKS + ".fight"; public static final String FIGHT_ANGLE = FIGHT + ".angle"; public static final String FIGHT_CRITICAL = FIGHT + ".critical"; public static final String FIGHT_DIRECTION = FIGHT + ".direction"; @@ -149,7 +149,7 @@ public class Permissions { * 888 * 888 */ - private static final String INVENTORY = CHECKS + ".inventory"; + public static final String INVENTORY = CHECKS + ".inventory"; public static final String INVENTORY_DROP = INVENTORY + ".drop"; public static final String INVENTORY_FASTCLICK = INVENTORY + ".fastclick"; public static final String INVENTORY_INSTANTBOW = INVENTORY + ".instantbow"; @@ -165,7 +165,7 @@ public class Permissions { * , 88P * "8",P" */ - private static final String MOVING = CHECKS + ".moving"; + public static final String MOVING = CHECKS + ".moving"; public static final String MOVING_BOATSANYWHERE = MOVING + ".boatsanywhere"; public static final String MOVING_CREATIVEFLY = MOVING + ".creativefly"; public static final String MOVING_MOREPACKETS = MOVING + ".morepackets"; diff --git a/src/fr/neatmonster/nocheatplus/utilities/ActionFrequency.java b/src/fr/neatmonster/nocheatplus/utilities/ActionFrequency.java index da22dda8..99055abd 100644 --- a/src/fr/neatmonster/nocheatplus/utilities/ActionFrequency.java +++ b/src/fr/neatmonster/nocheatplus/utilities/ActionFrequency.java @@ -107,8 +107,14 @@ public class ActionFrequency { cf *= factor; } return score; - - + } + + /** + * Get reference time. + * @return + */ + public final long lastAccess(){ + return time; } } diff --git a/src/fr/neatmonster/nocheatplus/utilities/BlockCache.java b/src/fr/neatmonster/nocheatplus/utilities/BlockCache.java index 43a67900..d063f074 100644 --- a/src/fr/neatmonster/nocheatplus/utilities/BlockCache.java +++ b/src/fr/neatmonster/nocheatplus/utilities/BlockCache.java @@ -1,8 +1,5 @@ package fr.neatmonster.nocheatplus.utilities; -import java.util.HashMap; -import java.util.Map; - import net.minecraft.server.IBlockAccess; import net.minecraft.server.Material; import net.minecraft.server.TileEntity; @@ -11,70 +8,25 @@ import net.minecraft.server.Vec3DPool; import org.bukkit.World; import org.bukkit.craftbukkit.CraftWorld; +import fr.neatmonster.nocheatplus.utilities.ds.CoordMap; + /** * Access to type-ids and data using caching techniques. * @author mc_dev * */ public class BlockCache implements IBlockAccess{ - /** - * TODO: Make a map for faster queries (without object creation). - * TODO: Not sure the prime numbers are too big for normal use. - * @author mc_dev - * - */ - private static class Pos3D{ - private static final int p1 = 73856093; - private static final int p2 = 19349663; - private static final int p3 = 83492791; - // Cube coordinates: - public final int x; - public final int y; - public final int z; - public final int hash; - /** - * - * @param x - * @param y - * @param z - * @param size - */ - public Pos3D (final int x, final int y, final int z){ - // Cube related coordinates: - this.x = x; - this.y = y; - this.z = z; - // Hash - hash = getHash(this.x, this.y, this.z); - } - - @Override - public final boolean equals(final Object obj) { - if (obj instanceof Pos3D){ - final Pos3D other = (Pos3D) obj; - return other.x == x && other.y == y && other.z == z; - } - else return false; - } - - @Override - public final int hashCode() { - return hash; - } - - public static final int getHash(final int x, final int y, final int z) { - return p1 * x ^ p2 * y ^ p3 * z; - } - } /** * For getting ids. */ private IBlockAccess access = null; + /** Cached type-ids. */ - private final Map idMap = new HashMap(); - /** Cahced data values. */ - private final Map dataMap = new HashMap(); + private final CoordMap idMap = new CoordMap(); + + /** Cached data values. */ + private final CoordMap dataMap = new CoordMap(); // TODO: maybe make very fast access arrays for the ray tracing checks. // private int[] id = null; @@ -117,23 +69,21 @@ public class BlockCache implements IBlockAccess{ } - public int getTypeId(final int x, final int y, final int z){ - final Pos3D pos = new Pos3D(x, y, z); - final Integer pId = idMap.get(pos); - if (pId != null) return pId; - final Integer nId = access.getTypeId(x, y, z); - idMap.put(pos, nId); - return nId; - } - - public int getData(final int x, final int y, final int z){ - final Pos3D pos = new Pos3D(x, y, z); - final Integer pData = dataMap.get(pos); - if (pData != null) return pData; - final Integer nData = access.getData(x, y, z); - dataMap.put(pos, nData); - return nData; - } + public int getTypeId(final int x, final int y, final int z) { + final Integer pId = idMap.get(x, y, z); + if (pId != null) return pId; + final Integer nId = access.getTypeId(x, y, z); + idMap.put(x, y, z, nId); + return nId; + } + + public int getData(final int x, final int y, final int z) { + final Integer pData = dataMap.get(x, y, z); + if (pData != null) return pData; + final Integer nData = access.getData(x, y, z); + dataMap.put(x, y, z, nData); + return nData; + } /** * Not Optimized. diff --git a/src/fr/neatmonster/nocheatplus/utilities/BlockProperties.java b/src/fr/neatmonster/nocheatplus/utilities/BlockProperties.java index 70fc9770..879843e0 100644 --- a/src/fr/neatmonster/nocheatplus/utilities/BlockProperties.java +++ b/src/fr/neatmonster/nocheatplus/utilities/BlockProperties.java @@ -261,6 +261,8 @@ public class BlockProperties { public static final int F_HEIGHT150 = 0x40; /** The player can stand on these, sneaking or not. */ public static final int F_GROUND = 0x80; + /** 1 block height. */ + public static final int F_HEIGHT100 = 0x100; static{ init(); @@ -328,7 +330,7 @@ public class BlockProperties { for (final Material mat : new Material[] {Material.NETHER_BRICK_STAIRS, Material.COBBLESTONE_STAIRS, Material.SMOOTH_STAIRS, Material.BRICK_STAIRS, Material.SANDSTONE_STAIRS, Material.WOOD_STAIRS, Material.SPRUCE_WOOD_STAIRS, Material.BIRCH_WOOD_STAIRS, Material.JUNGLE_WOOD_STAIRS}){ - blockFlags[mat.getId()] |= F_STAIRS; + blockFlags[mat.getId()] |= F_STAIRS | F_HEIGHT100; } // WATER. for (final Material mat : new Material[]{ @@ -356,6 +358,7 @@ public class BlockProperties { }){ blockFlags[mat.getId()] |= F_GROUND; } + blockFlags[Material.ENDER_PORTAL_FRAME.getId()] |= F_HEIGHT100; // Ignore for passable. for (final Material mat : new Material[]{ Material.WOOD_PLATE, Material.STONE_PLATE, @@ -502,6 +505,7 @@ public class BlockProperties { blocks[Material.SKULL.getId()] = new BlockProps(noTool, 8.5f, secToMs(1.45)); // TODO blockFlags[Material.SKULL.getId()] |= F_GROUND; blocks[Material.ANVIL.getId()] = new BlockProps(woodPickaxe, 5f); // TODO + blockFlags[Material.FLOWER_POT.getId()] |= F_GROUND; // Indestructible. for (Material mat : new Material[]{ @@ -977,6 +981,17 @@ public class BlockProperties { final IBlockAccess access = ((org.bukkit.craftbukkit.CraftWorld) loc.getWorld()).getHandle(); return isPassable(access, loc.getX(), loc.getY(), loc.getZ(), access.getTypeId(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); } + + /** + * Convenience method to allow using an already fetched or prepared IBlockAccess. + * @param access + * @param loc + * @return + */ + public static final boolean isPassable(final IBlockAccess access, final Location loc) + { + return isPassable(access, loc.getX(), loc.getY(), loc.getZ(), access.getTypeId(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + } /** * API access to read extra properties from files. @@ -1093,7 +1108,7 @@ public class BlockProperties { final double bmaxY; // if ((blockFlags[id] & F_HEIGHT150) != 0) block.maxY = 1.5; if ((blockFlags[id] & F_HEIGHT150) != 0) bmaxY = 1.5; - else if (isStairs(id)) bmaxY = 1.0; + else if ((blockFlags[id] & F_HEIGHT100) != 0) bmaxY = 1.0; else bmaxY = block.y(); // maxY // if (minX > block.maxX + x || maxX < block.minX + x) return false; // else if (minY > block.maxY + y || maxY < block.minY + y) return false; diff --git a/src/fr/neatmonster/nocheatplus/utilities/CheckUtils.java b/src/fr/neatmonster/nocheatplus/utilities/CheckUtils.java index 7c79e702..d2157b73 100644 --- a/src/fr/neatmonster/nocheatplus/utilities/CheckUtils.java +++ b/src/fr/neatmonster/nocheatplus/utilities/CheckUtils.java @@ -8,6 +8,7 @@ import java.util.Collection; import java.util.List; import java.util.logging.Logger; + import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; @@ -18,194 +19,195 @@ import org.bukkit.entity.Player; import org.bukkit.util.Vector; /** - * The Class CheckUtils. + * Random auxiliary gear, some might have general quality. */ public class CheckUtils { - /** The file logger. */ - public static Logger fileLogger = null; - - /** Decimal format for "#.###" */ - public static final DecimalFormat fdec3 = new DecimalFormat(); - - static{ - DecimalFormatSymbols sym = fdec3.getDecimalFormatSymbols(); - sym.setDecimalSeparator('.'); - fdec3.setDecimalFormatSymbols(sym); - fdec3.setMaximumFractionDigits(3); - fdec3.setMinimumIntegerDigits(1); - } + /** The file logger. */ + public static Logger fileLogger = null; - /** - * Check if a player looks at a target of a specific size, with a specific precision value (roughly). - * - * @param player - * the player - * @param targetX - * the target x - * @param targetY - * the target y - * @param targetZ - * the target z - * @param targetWidth - * the target width - * @param targetHeight - * the target height - * @param precision - * the precision - * @return the double - */ - public static double directionCheck(final Player player, final double targetX, final double targetY, - final double targetZ, final double targetWidth, final double targetHeight, final double precision) { + /** Decimal format for "#.###" */ + public static final DecimalFormat fdec3 = new DecimalFormat(); - // Get the eye location of the player. - final Location eyes = player.getEyeLocation(); + static { + DecimalFormatSymbols sym = fdec3.getDecimalFormatSymbols(); + sym.setDecimalSeparator('.'); + fdec3.setDecimalFormatSymbols(sym); + fdec3.setMaximumFractionDigits(3); + fdec3.setMinimumIntegerDigits(1); + } - final double factor = Math.sqrt(Math.pow(eyes.getX() - targetX, 2) + Math.pow(eyes.getY() - targetY, 2) - + Math.pow(eyes.getZ() - targetZ, 2)); + /** + * Check if a player looks at a target of a specific size, with a specific + * precision value (roughly). + * + * @param player + * the player + * @param targetX + * the target x + * @param targetY + * the target y + * @param targetZ + * the target z + * @param targetWidth + * the target width + * @param targetHeight + * the target height + * @param precision + * the precision + * @return the double + */ + public static double directionCheck(final Player player, final double targetX, final double targetY, final double targetZ, final double targetWidth, final double targetHeight, final double precision) + { - // Get the view direction of the player. - final Vector direction = eyes.getDirection(); + // Get the eye location of the player. + final Location eyes = player.getEyeLocation(); - final double x = targetX - eyes.getX(); - final double y = targetY - eyes.getY(); - final double z = targetZ - eyes.getZ(); + final double factor = Math.sqrt(Math.pow(eyes.getX() - targetX, 2) + Math.pow(eyes.getY() - targetY, 2) + Math.pow(eyes.getZ() - targetZ, 2)); - final double xPrediction = factor * direction.getX(); - final double yPrediction = factor * direction.getY(); - final double zPrediction = factor * direction.getZ(); + // Get the view direction of the player. + final Vector direction = eyes.getDirection(); - double off = 0.0D; + final double x = targetX - eyes.getX(); + final double y = targetY - eyes.getY(); + final double z = targetZ - eyes.getZ(); - off += Math.max(Math.abs(x - xPrediction) - (targetWidth / 2 + precision), 0.0D); - off += Math.max(Math.abs(z - zPrediction) - (targetWidth / 2 + precision), 0.0D); - off += Math.max(Math.abs(y - yPrediction) - (targetHeight / 2 + precision), 0.0D); + final double xPrediction = factor * direction.getX(); + final double yPrediction = factor * direction.getY(); + final double zPrediction = factor * direction.getZ(); - if (off > 1) - off = Math.sqrt(off); + double off = 0.0D; - return off; - } + off += Math.max(Math.abs(x - xPrediction) - (targetWidth / 2 + precision), 0.0D); + off += Math.max(Math.abs(z - zPrediction) - (targetWidth / 2 + precision), 0.0D); + off += Math.max(Math.abs(y - yPrediction) - (targetHeight / 2 + precision), 0.0D); - /** - * Calculate the distance between two location, because for Bukkit distance is the distance squared and - * distanceSquared is the distance non-squared. - * - * @param location1 - * the location1 - * @param location2 - * the location2 - * @return the double - */ - public static double distance(final Location location1, final Location location2) { - return Math.sqrt(Math.pow(location2.getX() - location1.getX(), 2) - + Math.pow(location2.getY() - location1.getY(), 2) + Math.pow(location2.getZ() - location1.getZ(), 2)); - } + if (off > 1) off = Math.sqrt(off); - /** - * Return if the two Strings are similar based on the given threshold. - * - * @param s - * the first String, must not be null - * @param t - * the second String, must not be null - * @param threshold - * the minimum value of the correlation coefficient - * @return result true if the two Strings are similar, false otherwise - */ - public static boolean isSimilar(final String s, final String t, final float threshold) { - return 1.0f - (float) levenshteinDistance(s, t) / Math.max(1.0, Math.max(s.length(), t.length())) > threshold; - } + return off; + } - /** - * Find the Levenshtein distance between two Strings. - * - * This is the number of changes needed to change one String into another, where each change is a single character - * modification (deletion, insertion or substitution). - * - * @param s - * the first String, must not be null - * @param t - * the second String, must not be null - * @return result distance - */ - private static int levenshteinDistance(CharSequence s, CharSequence t) { - if (s == null || t == null) - throw new IllegalArgumentException("Strings must not be null"); + /** + * Calculate the distance between two location, because for Bukkit distance + * is the distance squared and distanceSquared is the distance non-squared. + * + * @param location1 + * the location1 + * @param location2 + * the location2 + * @return the double + */ + public static double distance(final Location location1, final Location location2) + { + return Math.sqrt(Math.pow(location2.getX() - location1.getX(), 2) + Math.pow(location2.getY() - location1.getY(), 2) + Math.pow(location2.getZ() - location1.getZ(), 2)); + } - int n = s.length(); - int m = t.length(); + /** + * Return if the two Strings are similar based on the given threshold. + * + * @param s + * the first String, must not be null + * @param t + * the second String, must not be null + * @param threshold + * the minimum value of the correlation coefficient + * @return result true if the two Strings are similar, false otherwise + */ + public static boolean isSimilar(final String s, final String t, final float threshold) + { + return 1.0f - (float) levenshteinDistance(s, t) / Math.max(1.0, Math.max(s.length(), t.length())) > threshold; + } - if (n == 0) - return m; - else if (m == 0) - return n; + /** + * Find the Levenshtein distance between two Strings. + * + * This is the number of changes needed to change one String into another, + * where each change is a single character modification (deletion, insertion or substitution). + * + * @param s + * the first String, must not be null + * @param t + * the second String, must not be null + * @return result distance + */ + private static int levenshteinDistance(CharSequence s, CharSequence t) { + if (s == null || t == null) throw new IllegalArgumentException("Strings must not be null"); - if (n > m) { - final CharSequence tmp = s; - s = t; - t = tmp; - n = m; - m = t.length(); - } + int n = s.length(); + int m = t.length(); - int p[] = new int[n + 1]; - int d[] = new int[n + 1]; - int _d[]; + if (n == 0) return m; + else if (m == 0) return n; - int i; - int j; + if (n > m) { + final CharSequence tmp = s; + s = t; + t = tmp; + n = m; + m = t.length(); + } - char t_j; + int p[] = new int[n + 1]; + int d[] = new int[n + 1]; + int _d[]; - int cost; + int i; + int j; - for (i = 0; i <= n; i++) - p[i] = i; + char t_j; - for (j = 1; j <= m; j++) { - t_j = t.charAt(j - 1); - d[0] = j; + int cost; - for (i = 1; i <= n; i++) { - cost = s.charAt(i - 1) == t_j ? 0 : 1; - d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost); - } + for (i = 0; i <= n; i++) + p[i] = i; - _d = p; - p = d; - d = _d; - } + for (j = 1; j <= m; j++) { + t_j = t.charAt(j - 1); + d[0] = j; - return p[n]; - } - - /** - * Join parts with link. - * @param input - * @param link - * @return - */ - public static String join(final Collection input, final String link){ - final StringBuilder builder = new StringBuilder(Math.max(300, input.size() * 10)); - boolean first = true; - for (final Object obj : input){ - if (!first) builder.append(link); - builder.append(obj.toString()); - first = false; - } - return builder.toString(); - } - - /** - * Convenience method. - * @param parts - * @param link - * @return - */ - public static boolean scheduleOutputJoined(final List parts, String link){ - return scheduleOutput(join(parts, link)); - } + for (i = 1; i <= n; i++) { + cost = s.charAt(i - 1) == t_j ? 0 : 1; + d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost); + } + + _d = p; + p = d; + d = _d; + } + + return p[n]; + } + + /** + * Join parts with link. + * + * @param input + * @param link + * @return + */ + public static String join(final Collection input, final String link) + { + final StringBuilder builder = new StringBuilder(Math.max(300, input.size() * 10)); + boolean first = true; + for (final Object obj : input) { + if (!first) builder.append(link); + builder.append(obj.toString()); + first = false; + } + return builder.toString(); + } + + /** + * Convenience method. + * + * @param parts + * @param link + * @return + */ + public static boolean scheduleOutputJoined(final List parts, String link) + { + return scheduleOutput(join(parts, link)); + } /** * Schedule a message to be output by the bukkit logger. @@ -307,15 +309,15 @@ public class CheckUtils { /** * Get the height from getLocation().getY() to head / above head.
* NOTE: Currently this is pretty much useless, it returns 1.0 most of the time. + * * @param entity * @return */ - public static double getHeight(final Entity entity) { - final net.minecraft.server.Entity mcEntity = ((CraftEntity)entity).getHandle(); - final double entityHeight = mcEntity.height; - if (entity instanceof LivingEntity){ - return Math.max(((LivingEntity) entity).getEyeHeight(), entityHeight); - } - else return mcEntity.height; - } + public static double getHeight(final Entity entity) { + final net.minecraft.server.Entity mcEntity = ((CraftEntity) entity).getHandle(); + final double entityHeight = mcEntity.height; + if (entity instanceof LivingEntity) { + return Math.max(((LivingEntity) entity).getEyeHeight(), entityHeight); + } else return mcEntity.height; + } } diff --git a/src/fr/neatmonster/nocheatplus/utilities/PlayerLocation.java b/src/fr/neatmonster/nocheatplus/utilities/PlayerLocation.java index 8ff4420c..c0db195c 100644 --- a/src/fr/neatmonster/nocheatplus/utilities/PlayerLocation.java +++ b/src/fr/neatmonster/nocheatplus/utilities/PlayerLocation.java @@ -334,10 +334,13 @@ public class PlayerLocation { onGround = BlockProperties.isOnGround(getBlockAccess(), minX, minY - yOnGround, minZ, maxX, maxY + 0.25, maxZ); if (!onGround){ // TODO: Probably check other ids too before doing this ? - final double d0 = 0.25D; + final double d0 = 1D; //0.25D; +// for (org.bukkit.entity.Entity test : entity.getBukkitEntity().getNearbyEntities(1, 1, 1)){ +// System.out.println("*" + test.getType()); +// } final AxisAlignedBB box = useBox.b(minX - d0, minY - getyOnGround() - d0, minZ - d0, maxX + d0, maxY + d0, maxZ + d0); @SuppressWarnings("rawtypes") - List list = worldServer.getEntities(entity, box); + final List list = worldServer.getEntities(entity, box); @SuppressWarnings("rawtypes") Iterator iterator = list.iterator(); while (iterator.hasNext()) { diff --git a/src/fr/neatmonster/nocheatplus/utilities/ds/CoordMap.java b/src/fr/neatmonster/nocheatplus/utilities/ds/CoordMap.java new file mode 100644 index 00000000..79924ea6 --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/utilities/ds/CoordMap.java @@ -0,0 +1,162 @@ +package fr.neatmonster.nocheatplus.utilities.ds; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * Map int coordinates to values (just for fun). Intended for Minecraft coordinates, probably not for too high values.
+ * This implementation is not thread safe, though changing values and get/contains should work if the map stays unchanged. + * @author mc_dev + * + * @param + */ +public class CoordMap { + + private static final int p1 = 73856093; + private static final int p2 = 19349663; + private static final int p3 = 83492791; + + private static final int getHash(final int x, final int y, final int z) { + return p1 * x ^ p2 * y ^ p3 * z; + } + + private static final class Entry{ + protected final int x; + protected final int y; + protected final int z; + protected V value; + protected final int hash; + public Entry(final int x, final int y, final int z, final V value, final int hash){ + this.x = x; + this.y = y; + this.z = z; + this.value = value; + this.hash = hash; + } + } + + // Core data. + private final float loadFactor; + private List>[] entries; + /** Current size. */ + private int size = 0; + + public CoordMap(){ + this(10, 0.75f); + } + + public CoordMap(final int initialCapacity){ + this(initialCapacity, 0.75f); + } + + /** + * + * @param initialCapacity Initial internal array size.
+ * TODO: change to expected number of elements (len = cap/load). + * @param loadFactor + */ + @SuppressWarnings("unchecked") + public CoordMap(final int initialCapacity, float loadFactor){ + this.loadFactor = loadFactor; + entries = new List[initialCapacity]; + } + + /** + * Check if the map contains a value for the given coordinates.
+ * NOTE: Delegates to get, use get for fastest checks. + * @param x + * @param y + * @param z + * @return + */ + public final boolean contains(final int x, final int y, final int z){ + return get(x,y,z) != null; + } + + /** + * Get the value if there is a mapping for the given coordinates. + * @param x + * @param y + * @param z + * @return + */ + public final V get(final int x, final int y, final int z){ + final int hash = getHash(x, y, z); + final int slot = Math.abs(hash) % entries.length; + final List> bucket = entries[slot]; + if (bucket == null) return null;; + for (final Entry entry : bucket){ + if (hash == entry.hash && x == entry.x && z == entry.z && y == entry.y) return entry.value; + } + return null; + } + + /** + * Add value with the coordinates + hash from the last contains call. + * @param value + * @return If a value was replaced. + */ + public final boolean put(final int x, final int y, final int z, final V value){ + final int hash = getHash(x, y, z); + final int absHash = Math.abs(hash); + int slot = absHash % entries.length; + List> bucket = entries[slot]; + if (bucket != null){ + for (final Entry entry : bucket){ + if (hash == entry.hash && x == entry.x && y == entry.z && z == entry.y){ + entry.value = value; + return true; + } + } + } + else if (size + 1 > entries.length * loadFactor){ + resize(size + 1); + slot = absHash % entries.length; + bucket = entries[slot]; + } + if (bucket == null) { + // TODO: use array list ? + bucket = new LinkedList>(); + entries[slot] = bucket; + } + entries[slot] = bucket; + bucket.add(new Entry(x, y, z, value, hash)); + size ++; + return false; + } + + private final void resize(final int size) { + // TODO: other capacity? + final int newCapacity = (int) ((size + 4) / loadFactor); + @SuppressWarnings("unchecked") + final List>[] newEntries = new List[newCapacity]; + for (int oldSlot = 0; oldSlot < entries.length; oldSlot++){ + final List> oldBucket = entries[oldSlot]; + if (oldBucket != null){ + for (final Entry entry : oldBucket){ + final int newSlot = Math.abs(entry.hash) % newCapacity; + List> newBucket = newEntries[oldSlot]; + if (newBucket == null){ + newBucket = new LinkedList>(); + newEntries[newSlot] = newBucket; + } + newBucket.add(entry); + } + oldBucket.clear(); + } + entries[oldSlot] = null; + } + entries = newEntries; + } + + public final int size(){ + return size; + } + + public void clear(){ + size = 0; + Arrays.fill(entries, null); + // TODO: resize ? + } +}