Safety fuck up commit.

This commit is contained in:
asofold 2012-11-06 07:56:53 +01:00
parent 81ec563474
commit 3a1e9fe7d8
20 changed files with 1025 additions and 619 deletions

View File

@ -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"),

View File

@ -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,49 +98,64 @@ 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.
* Special purpose check types (likely not actual checks).
*/
private CheckType() {}
/**
* 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;
private CheckType() {
this(null, null, null);
}
/**
* Instantiates a new check type.
*
* @param parent
* the parent
* 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);
}
/**
* Constructor for sub-checks grouped under another check type.
* @param parent
* @param permission
* the permission
*/
private CheckType(final CheckType parent, final String permission) {
this(parent, permission, parent.getConfigFactory(), parent.getDataFactory());
}
/**
* 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;
configFactory = parent.getConfigFactory();
dataFactory = parent.getDataFactory();
this.permission = permission;
this.configFactory = configFactory;
this.dataFactory = dataFactory;
}
/**
@ -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.<br>
* @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<CheckDataFactory> factories = new HashSet<CheckDataFactory>();
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);
}
}

View File

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

View File

@ -116,9 +116,9 @@ public class FightListener implements Listener {
cancelled = true;
}
if (!cancelled && angle.isEnabled(player)){
// Improbable yaw.
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;
@ -126,10 +126,6 @@ public class FightListener implements Listener {
// Angle check.
if (angle.check(player, worldChanged)) cancelled = true;
}
else{
// Always feed yaw rate here.
Combined.feedYawRate(player, player.getLocation().getYaw(), now, worldName);
}
if (!cancelled && critical.isEnabled(player) && critical.check(player))
cancelled = true;

View File

@ -88,6 +88,8 @@ public class MovingData extends ACheckData {
public double fromX = Double.MAX_VALUE, fromY, fromZ;
/** Last to coordinates. */
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;
@ -153,6 +155,24 @@ public class MovingData extends ACheckData {
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();
hDistSum.clear(now);

View File

@ -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 ?
}
}
/**
@ -352,17 +362,8 @@ public class MovingListener implements Listener {
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;
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
@ -388,11 +389,16 @@ public class MovingListener implements Listener {
Location newTo = null;
if (passable.isEnabled(player)) newTo = passable.check(player, pFrom, pTo, data, cc);
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 (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
} 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,13 +436,16 @@ 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);
@ -518,13 +530,13 @@ public class MovingListener implements Listener {
}
/**
* When a player respawns, all information related to the moving checks becomes invalid.
* When a player respawns, all information related to the moving checks
* becomes invalid.
*
* @param event
* the event
*/
@EventHandler(
priority = EventPriority.MONITOR)
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerRespawn(final PlayerRespawnEvent event) {
/*
* ____ _ ____
@ -534,9 +546,14 @@ public class MovingListener implements Listener {
* |_| |_|\__,_|\__, |\___|_| |_| \_\___||___/ .__/ \__,_| \_/\_/ |_| |_|
* |___/ |_|
*/
final MovingData data = MovingData.getData(event.getPlayer());
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();
}
}
/**
@ -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) {
/*
* ____ _ _____ _ _
@ -566,17 +582,22 @@ public class MovingListener implements Listener {
// 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)){
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);
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.
// 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();
@ -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;
}
}

View File

@ -19,7 +19,8 @@ public class Passable extends Check {
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.
@ -30,23 +31,26 @@ public class Passable extends Check {
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.
// 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)
// 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 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.
// 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()))){
} 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;
}
@ -54,14 +58,19 @@ public class Passable extends Check {
}
}
}
// 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.
// 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){
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()))) {
@ -74,19 +83,16 @@ public class Passable extends Check {
newTo.setPitch(to.getPitch());
return newTo;
}
}
else{
} else {
data.passableVL *= 0.99;
}
return null;
}
@Override
protected Map<ParameterName, String> getParameterMap(final ViolationData violationData) {
// TODO Auto-generated method stub
protected Map<ParameterName, String> getParameterMap(final ViolationData violationData)
{
return super.getParameterMap(violationData);
}
}

View File

@ -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<String> tags = new ArrayList<String>(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;
@ -112,49 +118,55 @@ public class SurvivalFly extends Check {
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 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.
// 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;
// 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 (!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).
// 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.
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 (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.
// Maybe don't adapt the setback (unless null)!
// data.setBack = from.getLocation();
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 = 0; // Might conflict, should probably fetch.
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;
if (cc.debug) System.out.println(player.getName() + " Y-INCONSISTENCY WORKAROUND USED");
resetFrom = true; // Note: if removing this, other conditions need to check noFallAssume...
tags.add("lostground");
}
}
@ -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;
// 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 = hDistance;
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)
// 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){
@ -289,41 +305,39 @@ public class SurvivalFly extends Check {
vDistanceAboveLimit = to.getY() - data.setBack.getY() - vAllowedDistance;
// System.out.println("vda = " +vDistanceAboveLimit + " / vc = " + data.verticalVelocityCounter + " / vf = " + data.verticalFreedom + " / v = " + player.getVelocity().length());
// Step can also be blocked.
if ((fromOnGround || data.noFallAssumeGround) && toOnGround && Math.abs(yDistance - 1D) <= cc.yStep && vDistanceAboveLimit <= 0D
&& !player.hasPermission(Permissions.MOVING_SURVIVALFLY_STEP))
// 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);
}
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 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");
// }
}
}
@ -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();
@ -385,4 +401,58 @@ public class SurvivalFly extends Check {
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.<br>
* 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<String> 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;
}
}

View File

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

View File

@ -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.<br>
* NOTE: For CheckType-specific data removal, IHaveCheckType should be implemented, otherwise this data might get ignored until plugin-disable.
* @author mc_dev
*
*/

View File

@ -121,22 +121,19 @@ public class NCPExemptionManager {
public static Listener getListener() {
return new Listener() {
@SuppressWarnings("unused")
@EventHandler(
ignoreCancelled = true, priority = EventPriority.LOWEST)
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerJoin(final PlayerJoinEvent event) {
NCPExemptionManager.registerPlayer(event.getPlayer());
}
@SuppressWarnings("unused")
@EventHandler(
ignoreCancelled = true, priority = EventPriority.MONITOR)
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerQuit(final PlayerQuitEvent event) {
NCPExemptionManager.tryToRemove(event.getPlayer());
}
@SuppressWarnings("unused")
@EventHandler(
ignoreCancelled = true, priority = EventPriority.MONITOR)
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onPlayerKick(final PlayerKickEvent event) {
NCPExemptionManager.tryToRemove(event.getPlayer());
}

View File

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

View File

@ -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.<br>
* Originally intended as temporary or intermediate design, this might help reorganizing the API at some point.<br>
* 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<CheckType, Map<String, ExecutionHistory>> executionHistories = new HashMap<CheckType, Map<String,ExecutionHistory>>();
/**
* 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.<br>
* 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.<br>
* NOTE: This method is not really meant ot be used from outside NCP.
* @param type
* @param histories
*/
public static void registerExecutionHistory(CheckType type, Map<String, ExecutionHistory> 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.<br>
* @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<CheckDataFactory> factories = new HashSet<CheckDataFactory>();
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).<br>
* 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);

View File

@ -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,7 +89,7 @@ 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_LIQUIDS = BLOCKPLACE_AGAINST + ".liquids";
@ -106,7 +106,7 @@ 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";
@ -117,7 +117,7 @@ public class Permissions {
/*
* Combined !
*/
private static final String COMBINED = CHECKS + ".combined";
public static final String COMBINED = CHECKS + ".combined";
public static final String COMBINED_IMPROBABLE = COMBINED + ".improbable";
/*
@ -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";

View File

@ -107,8 +107,14 @@ public class ActionFrequency {
cf *= factor;
}
return score;
}
/**
* Get reference time.
* @return
*/
public final long lastAccess(){
return time;
}
}

View File

@ -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<Pos3D, Integer> idMap = new HashMap<Pos3D, Integer>();
/** Cahced data values. */
private final Map<Pos3D, Integer> dataMap = new HashMap<Pos3D, Integer>();
private final CoordMap<Integer> idMap = new CoordMap<Integer>();
/** Cached data values. */
private final CoordMap<Integer> dataMap = new CoordMap<Integer>();
// TODO: maybe make very fast access arrays for the ray tracing checks.
// private int[] id = null;
@ -118,20 +70,18 @@ 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);
final Integer pId = idMap.get(x, y, z);
if (pId != null) return pId;
final Integer nId = access.getTypeId(x, y, z);
idMap.put(pos, nId);
idMap.put(x, y, z, 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);
final Integer pData = dataMap.get(x, y, z);
if (pData != null) return pData;
final Integer nData = access.getData(x, y, z);
dataMap.put(pos, nData);
dataMap.put(x, y, z, nData);
return nData;
}

View File

@ -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[]{
@ -978,6 +982,17 @@ public class BlockProperties {
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.
* @param config
@ -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;

View File

@ -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,7 +19,7 @@ import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
/**
* The Class CheckUtils.
* Random auxiliary gear, some might have general quality.
*/
public class CheckUtils {
@ -37,7 +38,8 @@ public class CheckUtils {
}
/**
* Check if a player looks at a target of a specific size, with a specific precision value (roughly).
* Check if a player looks at a target of a specific size, with a specific
* precision value (roughly).
*
* @param player
* the player
@ -55,14 +57,13 @@ public class CheckUtils {
* 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) {
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 eye location of the player.
final Location eyes = player.getEyeLocation();
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 factor = Math.sqrt(Math.pow(eyes.getX() - targetX, 2) + Math.pow(eyes.getY() - targetY, 2) + Math.pow(eyes.getZ() - targetZ, 2));
// Get the view direction of the player.
final Vector direction = eyes.getDirection();
@ -81,15 +82,14 @@ public class CheckUtils {
off += Math.max(Math.abs(z - zPrediction) - (targetWidth / 2 + precision), 0.0D);
off += Math.max(Math.abs(y - yPrediction) - (targetHeight / 2 + precision), 0.0D);
if (off > 1)
off = Math.sqrt(off);
if (off > 1) off = Math.sqrt(off);
return off;
}
/**
* Calculate the distance between two location, because for Bukkit distance is the distance squared and
* distanceSquared is the distance non-squared.
* 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
@ -97,9 +97,9 @@ public class CheckUtils {
* 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));
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));
}
/**
@ -113,15 +113,16 @@ public class CheckUtils {
* 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) {
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;
}
/**
* 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).
* 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
@ -130,16 +131,13 @@ public class CheckUtils {
* @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 (s == null || t == null) throw new IllegalArgumentException("Strings must not be null");
int n = s.length();
int m = t.length();
if (n == 0)
return m;
else if (m == 0)
return n;
if (n == 0) return m;
else if (m == 0) return n;
if (n > m) {
final CharSequence tmp = s;
@ -182,11 +180,13 @@ public class CheckUtils {
/**
* Join parts with link.
*
* @param input
* @param link
* @return
*/
public static <O extends Object> String join(final Collection<O> input, final String link){
public static <O extends Object> String join(final Collection<O> input, final String link)
{
final StringBuilder builder = new StringBuilder(Math.max(300, input.size() * 10));
boolean first = true;
for (final Object obj : input) {
@ -199,11 +199,13 @@ public class CheckUtils {
/**
* Convenience method.
*
* @param parts
* @param link
* @return
*/
public static <O extends Object> boolean scheduleOutputJoined(final List<O> parts, String link){
public static <O extends Object> boolean scheduleOutputJoined(final List<O> parts, String link)
{
return scheduleOutput(join(parts, link));
}
@ -307,6 +309,7 @@ public class CheckUtils {
/**
* Get the height from getLocation().getY() to head / above head.<br>
* NOTE: Currently this is pretty much useless, it returns 1.0 most of the time.
*
* @param entity
* @return
*/
@ -315,7 +318,6 @@ public class CheckUtils {
final double entityHeight = mcEntity.height;
if (entity instanceof LivingEntity) {
return Math.max(((LivingEntity) entity).getEyeHeight(), entityHeight);
}
else return mcEntity.height;
} else return mcEntity.height;
}
}

View File

@ -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()) {

View File

@ -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.<br>
* This implementation is not thread safe, though changing values and get/contains should work if the map stays unchanged.
* @author mc_dev
*
* @param <V>
*/
public class CoordMap<V> {
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<V>{
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<Entry<V>>[] 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. <br>
* 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.<br>
* 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<Entry<V>> bucket = entries[slot];
if (bucket == null) return null;;
for (final Entry<V> 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<Entry<V>> bucket = entries[slot];
if (bucket != null){
for (final Entry<V> 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<Entry<V>>();
entries[slot] = bucket;
}
entries[slot] = bucket;
bucket.add(new Entry<V>(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<Entry<V>>[] newEntries = new List[newCapacity];
for (int oldSlot = 0; oldSlot < entries.length; oldSlot++){
final List<Entry<V>> oldBucket = entries[oldSlot];
if (oldBucket != null){
for (final Entry<V> entry : oldBucket){
final int newSlot = Math.abs(entry.hash) % newCapacity;
List<Entry<V>> newBucket = newEntries[oldSlot];
if (newBucket == null){
newBucket = new LinkedList<Entry<V>>();
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 ?
}
}