diff --git a/src/fr/neatmonster/nocheatplus/NoCheatPlus.java b/src/fr/neatmonster/nocheatplus/NoCheatPlus.java index 3310bc43..2c24abdf 100644 --- a/src/fr/neatmonster/nocheatplus/NoCheatPlus.java +++ b/src/fr/neatmonster/nocheatplus/NoCheatPlus.java @@ -45,6 +45,7 @@ import fr.neatmonster.nocheatplus.metrics.Metrics.Graph; import fr.neatmonster.nocheatplus.metrics.Metrics.Plotter; import fr.neatmonster.nocheatplus.metrics.MetricsData; import fr.neatmonster.nocheatplus.players.Permissions; +import fr.neatmonster.nocheatplus.utilities.BlockUtils; import fr.neatmonster.nocheatplus.utilities.LagMeasureTask; import fr.neatmonster.nocheatplus.utilities.TickTask; @@ -283,6 +284,9 @@ public class NoCheatPlus extends JavaPlugin implements Listener { if (currentVersion > configurationVersion) configOutdated = true; } catch (final Exception e) {} + + // Debug information about unknown blocks. + if (!config.getBoolean(ConfPaths.BLOCKBREAK_FASTBREAK_OLDCHECK)) BlockUtils.dumpBlocks(config.getBoolean(ConfPaths.BLOCKBREAK_FASTBREAK_DEBUG, false)); // false); // Tell the server administrator that we finished loading NoCheatPlus now. System.out.println("[NoCheatPlus] Version " + getDescription().getVersion() + " is enabled."); diff --git a/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakConfig.java b/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakConfig.java index f7e54ed1..348018ea 100644 --- a/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakConfig.java +++ b/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakConfig.java @@ -75,10 +75,13 @@ public class BlockBreakConfig extends ACheckConfig { public final ActionList directionActions; public final boolean fastBreakCheck; + public final boolean fastBreakDebug; public final int fastBreakBuckets; public final long fastBreakBucketDur; + public final float fastBreakBucketFactor; public final int fastBreakBuffer; public final long fastBreakContention; + public final long fastBreakDelay; public final int fastBreakInterval; public final boolean fastBreakOldCheck; public final ActionList fastBreakActions; @@ -105,10 +108,13 @@ public class BlockBreakConfig extends ACheckConfig { directionActions = data.getActionList(ConfPaths.BLOCKBREAK_DIRECTION_ACTIONS, Permissions.BLOCKBREAK_DIRECTION); fastBreakCheck = data.getBoolean(ConfPaths.BLOCKBREAK_FASTBREAK_CHECK); + fastBreakDebug = data.getBoolean(ConfPaths.BLOCKBREAK_FASTBREAK_DEBUG, false); // hidden fastBreakContention = data.getLong(ConfPaths.BLOCKBREAK_FASTBREAK_BUCKETS_CONTENTION, 2000); fastBreakBucketDur = data.getInt(ConfPaths.BLOCKBREAK_FASTBREAK_BUCKETS_DUR, 4000); + fastBreakBucketFactor = (float) data.getDouble(ConfPaths.BLOCKBREAK_FASTBREAK_BUCKETS_FACTOR, 0.99); fastBreakBuckets = data.getInt(ConfPaths.BLOCKBREAK_FASTBREAK_BUCKETS_N, 30); fastBreakBuffer = data.getInt(ConfPaths.BLOCKBREAK_FASTBREAK_BUFFER); + fastBreakDelay = data.getLong(ConfPaths.BLOCKBREAK_FASTBREAK_DELAY, 50); fastBreakInterval = data.getInt(ConfPaths.BLOCKBREAK_FASTBREAK_INTERVAL); fastBreakOldCheck = data.getBoolean(ConfPaths.BLOCKBREAK_FASTBREAK_OLDCHECK); fastBreakActions = data.getActionList(ConfPaths.BLOCKBREAK_FASTBREAK_ACTIONS, Permissions.BLOCKBREAK_FASTBREAK); diff --git a/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakData.java b/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakData.java index 974479ec..cd14cd6b 100644 --- a/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakData.java +++ b/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakData.java @@ -6,9 +6,10 @@ import java.util.Map; import org.bukkit.entity.Player; import fr.neatmonster.nocheatplus.checks.ACheckData; -import fr.neatmonster.nocheatplus.checks.ICheckData; import fr.neatmonster.nocheatplus.checks.CheckDataFactory; +import fr.neatmonster.nocheatplus.checks.ICheckData; import fr.neatmonster.nocheatplus.utilities.ActionFrequency; +import fr.neatmonster.nocheatplus.utilities.Stats; /* * M#"""""""'M dP dP M#"""""""'M dP @@ -76,6 +77,8 @@ public class BlockBreakData extends ACheckData { public int clickedX; public int clickedY; public int clickedZ; + + public final Stats stats; // Data of the fast break check. public final ActionFrequency fastBreakPenalties; @@ -95,6 +98,7 @@ public class BlockBreakData extends ACheckData { fastBreakBuffer = cc.fastBreakBuffer; fastBreakPenalties = new ActionFrequency(cc.fastBreakBuckets, cc.fastBreakBucketDur); wrongBlockVL = new ActionFrequency(6, 20000); + stats = cc.fastBreakDebug?(new Stats("NCP/FASTBREAK")):null; } } diff --git a/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakListener.java b/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakListener.java index c4596f42..b42e5e43 100644 --- a/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakListener.java +++ b/src/fr/neatmonster/nocheatplus/checks/blockbreak/BlockBreakListener.java @@ -96,6 +96,7 @@ public class BlockBreakListener implements Listener { // At least one check failed and demanded to cancel the event. if (cancelled) event.setCancelled(cancelled); + } /** diff --git a/src/fr/neatmonster/nocheatplus/checks/blockbreak/FastBreak.java b/src/fr/neatmonster/nocheatplus/checks/blockbreak/FastBreak.java index ba4780dd..c3bf03d1 100644 --- a/src/fr/neatmonster/nocheatplus/checks/blockbreak/FastBreak.java +++ b/src/fr/neatmonster/nocheatplus/checks/blockbreak/FastBreak.java @@ -8,6 +8,7 @@ import org.bukkit.entity.Player; import fr.neatmonster.nocheatplus.checks.Check; import fr.neatmonster.nocheatplus.checks.CheckType; import fr.neatmonster.nocheatplus.checks.combined.Improbable; +import fr.neatmonster.nocheatplus.players.Permissions; import fr.neatmonster.nocheatplus.utilities.BlockUtils; /* @@ -86,21 +87,26 @@ public class FastBreak extends Check { } else{ // First, check the game mode of the player and choose the right limit. - long breakingTime = Math.round((double) cc.fastBreakInterval / 100D * (double) BlockUtils.getBreakingDuration(block.getTypeId(), player.getItemInHand())); + long breakingTime = Math.round((double) cc.fastBreakInterval / 100D * (double) BlockUtils.getBreakingDuration(block.getTypeId(), player)); if (player.getGameMode() == GameMode.CREATIVE) breakingTime = Math.round((double) cc.fastBreakInterval / 100D * (double) CREATIVE); // fastBreakDamageTime is now first interact on block (!). - if (now - data.fastBreakDamageTime < breakingTime){ + final long elapsedTime = now - data.fastBreakDamageTime; + + // Check if the time used time is lower than expected. + if (elapsedTime + cc.fastBreakDelay < breakingTime){ // lag or cheat or Minecraft. - final long elapsedTime = now - data.fastBreakDamageTime; - + final long missingTime = breakingTime - elapsedTime; + + // Add as penalty data.fastBreakPenalties.add(now, (float) missingTime); - if (data.fastBreakPenalties.getScore(1f) > cc.fastBreakContention){ + // Only raise a violation, if the total penalty score exceeds the contention duration (for lag, delay). + if (data.fastBreakPenalties.getScore(cc.fastBreakBucketFactor) > cc.fastBreakContention){ // TODO: maybe add one absolute penalty time for big amounts to stop breaking until then data.fastBreakVL += missingTime; cancel = executeActions(player, data.fastBreakVL, missingTime, cc.fastBreakActions); @@ -113,9 +119,14 @@ public class FastBreak extends Check { data.fastBreakVL *= 0.9D; } + if (cc.fastBreakDebug && player.hasPermission(Permissions.ADMINISTRATION_DEBUG)){ + data.stats.addStats(data.stats.getId(Integer.toString(block.getTypeId())+"u", true), elapsedTime); + data.stats.addStats(data.stats.getId(Integer.toString(block.getTypeId())+ "r", true), breakingTime); + player.sendMessage(data.stats.getStatsStr(true)); + } + } - - + // Remember the block breaking time. data.fastBreakBreakTime = now; diff --git a/src/fr/neatmonster/nocheatplus/checks/blockbreak/WrongBlock.java b/src/fr/neatmonster/nocheatplus/checks/blockbreak/WrongBlock.java index 3f790a18..d21e09f6 100644 --- a/src/fr/neatmonster/nocheatplus/checks/blockbreak/WrongBlock.java +++ b/src/fr/neatmonster/nocheatplus/checks/blockbreak/WrongBlock.java @@ -25,6 +25,11 @@ public class WrongBlock extends Check { cancel = true; if (Improbable.check(player, 5.0f, now)) cancel = true; + // Reset, to prevent endless violation level farming. + data.fastBreakDamageTime = now; + data.clickedX = block.getX(); + data.clickedY = block.getY(); + data.clickedZ = block.getZ(); } return cancel; diff --git a/src/fr/neatmonster/nocheatplus/config/ConfPaths.java b/src/fr/neatmonster/nocheatplus/config/ConfPaths.java index ffed3fc5..2bb8b91a 100644 --- a/src/fr/neatmonster/nocheatplus/config/ConfPaths.java +++ b/src/fr/neatmonster/nocheatplus/config/ConfPaths.java @@ -66,12 +66,16 @@ public abstract class ConfPaths { public static final String BLOCKBREAK_DIRECTION_ACTIONS = BLOCKBREAK_DIRECTION + "actions"; private static final String BLOCKBREAK_FASTBREAK = BLOCKBREAK + "fastbreak."; + public static final String BLOCKBREAK_FASTBREAK_DEBUG = BLOCKBREAK_FASTBREAK + "debug"; public static final String BLOCKBREAK_FASTBREAK_CHECK = BLOCKBREAK_FASTBREAK + "active"; - private static final String BLOCKBREAK_FASTBREAK_BUCKETS = BLOCKBREAK + "buckets."; + private static final String BLOCKBREAK_FASTBREAK_BUCKETS = BLOCKBREAK + "buckets."; public static final String BLOCKBREAK_FASTBREAK_BUCKETS_CONTENTION = BLOCKBREAK_FASTBREAK_BUCKETS + "contention"; public static final String BLOCKBREAK_FASTBREAK_BUCKETS_N = BLOCKBREAK_FASTBREAK_BUCKETS + "number"; public static final String BLOCKBREAK_FASTBREAK_BUCKETS_DUR = BLOCKBREAK_FASTBREAK_BUCKETS + "duration"; + public static final String BLOCKBREAK_FASTBREAK_BUCKETS_FACTOR = BLOCKBREAK_FASTBREAK_BUCKETS + "factor"; + public static final String BLOCKBREAK_FASTBREAK_BUFFER = BLOCKBREAK_FASTBREAK + "buffer"; + public static final String BLOCKBREAK_FASTBREAK_DELAY = BLOCKBREAK_FASTBREAK + "delay"; public static final String BLOCKBREAK_FASTBREAK_INTERVAL = BLOCKBREAK_FASTBREAK + "interval"; public static final String BLOCKBREAK_FASTBREAK_OLDCHECK = BLOCKBREAK_FASTBREAK + "oldcheck"; public static final String BLOCKBREAK_FASTBREAK_ACTIONS = BLOCKBREAK_FASTBREAK + "actions"; @@ -414,5 +418,5 @@ public abstract class ConfPaths { * "8",P" */ public static final String STRINGS = "strings"; - + } diff --git a/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java b/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java index b7f9f656..93081f97 100644 --- a/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java +++ b/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java @@ -81,7 +81,7 @@ public class DefaultConfig extends ConfigFile { set(ConfPaths.BLOCKBREAK_REACH_ACTIONS, "cancel vl>5 log:breach:0:2:if cancel"); set(ConfPaths.BLOCKBREAK_WRONGBLOCK_CHECK, true); - set(ConfPaths.BLOCKBREAK_WRONGBLOCK_ACTIONS, "log:bwrong:0:5:if cancel vl>20 log:bwrong:0:5:if cancel cmd:kickwb"); + set(ConfPaths.BLOCKBREAK_WRONGBLOCK_ACTIONS, "cancel vl>5 log:bwrong:0:5:if cancel vl>20 log:bwrong:0:5:if cancel cmd:kickwb"); /* * 888 88b, 888 888 888 d8 d8 diff --git a/src/fr/neatmonster/nocheatplus/players/Permissions.java b/src/fr/neatmonster/nocheatplus/players/Permissions.java index a10c78ff..880e2fee 100644 --- a/src/fr/neatmonster/nocheatplus/players/Permissions.java +++ b/src/fr/neatmonster/nocheatplus/players/Permissions.java @@ -34,7 +34,10 @@ public class Permissions { public static final String ADMINISTRATION_REMOVEPLAYER = ADMINISTRATION + ".removeplayer"; public static final String ADMINISTRATION_TELL = ADMINISTRATION + ".tell"; public static final String ADMINISTRATION_TEMPKICK = ADMINISTRATION + ".tempkick"; + // Debug permission, for player spam (not in plugin.yml, currently). + public static final String ADMINISTRATION_DEBUG = ADMINISTRATION + ".debug"; + // Bypasses held extra from command permissions. private final static String BYPASS = NOCHEATPLUS + ".bypass"; public static final String BYPASS_DENY_LOGIN = BYPASS + "denylogin"; diff --git a/src/fr/neatmonster/nocheatplus/utilities/BlockUtils.java b/src/fr/neatmonster/nocheatplus/utilities/BlockUtils.java index 4048c748..41b048e8 100644 --- a/src/fr/neatmonster/nocheatplus/utilities/BlockUtils.java +++ b/src/fr/neatmonster/nocheatplus/utilities/BlockUtils.java @@ -1,31 +1,588 @@ package fr.neatmonster.nocheatplus.utilities; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; /** * Poperties of blocks. - * @author mc_dev * */ public class BlockUtils { + public static enum ToolType{ + NONE, + SWORD, + SHEARS, + SPADE, + AXE, + PICKAXE, +// HOE, + } + public static enum MaterialBase{ + NONE(0, 1f), + WOOD(1, 2f), + STONE(2, 4f), + IRON(3, 6f), + DIAMOND(4, 8f), + GOLD(5, 12f); + /** Index for array. */ + public final int index; + public final float breakMultiplier; + private MaterialBase(int index, float breakMultiplier){ + this.index = index; + this.breakMultiplier = breakMultiplier; + } + public static final MaterialBase getById(final int id){ + for (final MaterialBase base : MaterialBase.values()){ + if (base.index == id) return base; + } + throw new IllegalArgumentException("Bad id: " + id); + } + } /** Properties of a tool. */ public static class ToolProps{ - + public final ToolType toolType; + public final MaterialBase materialBase; + public ToolProps(ToolType toolType, MaterialBase materialBase){ + this.toolType = toolType; + this.materialBase = materialBase; + } + public String toString(){ + return "ToolProps("+toolType + "/"+materialBase+")"; + } } /** Properties of a block. */ public static class BlockProps{ - public float hardness = 1; - + public final ToolProps tool; + public final long[] breakingTimes; + public final float hardness; + public BlockProps(ToolProps tool, float hardness){ + this.tool = tool; + this.hardness = hardness; + breakingTimes = new long[6]; + for (int i = 0; i < 6; i++) { + final float multiplier; + if (tool.materialBase == null) + multiplier = 1f; + else if (i < tool.materialBase.index) + multiplier = 1f; + else + multiplier = MaterialBase.getById(i).breakMultiplier * 3.33f; + breakingTimes[i] = (long) (1000f * 5f * hardness / multiplier); + } + } + public BlockProps(ToolProps tool, float hardness, long[] breakingTimes){ + this.tool = tool; + this.breakingTimes = breakingTimes; + this.hardness = hardness; + } + public String toString(){ + return "BlockProps(" + hardness + " / " + tool.toString() + " / " + Arrays.toString(breakingTimes) + ")"; + } } + protected static final int maxBlocks = 4096; + + /** Properties by block id, might be extended to 4096 later for custom blocks.*/ + protected static final BlockProps[] blocks = new BlockProps[maxBlocks]; + + /** Map for the tool properties. */ + protected static Map tools = new HashMap(50, 0.5f); + + /** Breaking time for indestructible materials. */ + public static final long indestructible = Long.MAX_VALUE; + + /** Default tool properties (inappropriate tool). */ + public static final ToolProps noTool = new ToolProps(ToolType.NONE, MaterialBase.NONE); + + public static final ToolProps woodSword = new ToolProps(ToolType.SWORD, MaterialBase.WOOD); + + public static final ToolProps woodSpade = new ToolProps(ToolType.SPADE, MaterialBase.WOOD); + + public static final ToolProps woodPickaxe = new ToolProps(ToolType.PICKAXE, MaterialBase.WOOD); + + public static final ToolProps woodAxe = new ToolProps(ToolType.AXE, MaterialBase.WOOD); + + public static final ToolProps stonePickaxe = new ToolProps(ToolType.PICKAXE, MaterialBase.STONE); + + public static final ToolProps ironPickaxe = new ToolProps(ToolType.PICKAXE, MaterialBase.IRON); + + public static final ToolProps diamondPickaxe = new ToolProps(ToolType.PICKAXE, MaterialBase.DIAMOND); + + /** Times for instant breaking. */ + public static final long[] instantTimes = secToMs(0); + + public static final long[] leafTimes = secToMs(0.3); + + public static long[] glassTimes = secToMs(0.45); + + public static final long[] gravelTimes = secToMs(0.9, 0.45, 0.25, 0.15, 0.15, 0.1); + + public static long[] railsTimes = secToMs(1.05, 0.55, 0.3, 0.2, 0.15, 0.1); + + public static final long[] woodTimes = secToMs(3, 1.5, 0.75, 0.5, 0.4, 0.25); + + public static final long[] ironTimes = secToMs(15, 15, 1.15, 0.75, 0.6, 15); + + public static final long[] diamondTimes = secToMs(15, 15, 15, 0.75, 0.6, 15); + + private static final long[] indestructibleTimes = new long[] {indestructible, indestructible, indestructible, indestructible, indestructible, indestructible}; + + + /** Instantly breakable. */ + public static final BlockProps instantType = new BlockProps(noTool, 0, instantTimes); + + public static final BlockProps glassType = new BlockProps(noTool, 0.3f, glassTimes); + + public static final BlockProps gravelType = new BlockProps(woodSpade, 0.6f, gravelTimes); + /** Stone type blocks. */ + public static final BlockProps stoneType = new BlockProps(woodPickaxe, 1.5f); + + public static final BlockProps woodType = new BlockProps(woodAxe, 2, woodTimes); + + public static final BlockProps brickType = new BlockProps(woodPickaxe, 2); + + public static final BlockProps coalType = new BlockProps(woodPickaxe, 3); + + public static final BlockProps ironType = new BlockProps(stonePickaxe, 3, ironTimes); + + public static final BlockProps diamondType = new BlockProps(ironPickaxe, 3, diamondTimes); + + public static final BlockProps hugeMushroomType = new BlockProps(woodAxe, 0.2f, secToMs(0.3, 0.15, 0.1, 0.05, 0.05, 0.05)); + + public static final BlockProps leafType = new BlockProps(noTool, 0.2f, leafTimes); + + public static final BlockProps sandType = new BlockProps(woodSpade, 0.5f, secToMs(0.75, 0.4, 0.2, 0.15, 0.1, 0.1)); + + public static final BlockProps leverType = new BlockProps(noTool, 0.5f, secToMs(0.75)); + + public static final BlockProps sandStoneType = new BlockProps(woodPickaxe, 0.8f); + + public static final BlockProps pumpkinType = new BlockProps(woodAxe, 1, secToMs(1.5, 0.75, 0.4, 0.25, 0.2, 0.15)); + + public static final BlockProps chestType = new BlockProps(woodAxe, 2.5f, secToMs(3.75, 1.9, 0.95, 0.65, 0.5, 0.35)); + + public static final BlockProps woodDoorType = new BlockProps(woodAxe, 3.0f, secToMs(4.5, 2.25, 1.15, 0.75, 0.6, 0.4)); + + public static final BlockProps dispenserType = new BlockProps(woodPickaxe, 3.5f); + + public static final BlockProps ironDoorType = new BlockProps(woodPickaxe, 5); + + private static final BlockProps indestructibleType = new BlockProps(noTool, -1f, indestructibleTimes); + + /** Returned if unknown */ + public static final BlockProps defaultBlockProps = stoneType; + + protected static final Material[] instantMat = new Material[]{ + // Named in wiki. + Material.CROPS, + Material.TRIPWIRE_HOOK, Material.TRIPWIRE, + Material.TORCH, + Material.TNT, + Material.SUGAR_CANE_BLOCK, + Material.SAPLING, + Material.RED_ROSE, Material.YELLOW_FLOWER, + Material.REDSTONE_WIRE, + Material.REDSTONE_TORCH_ON, Material.REDSTONE_TORCH_OFF, + Material.DIODE_BLOCK_ON, Material.DIODE_BLOCK_OFF, + Material.PUMPKIN_STEM, + Material.NETHER_WARTS, + Material.BROWN_MUSHROOM, Material.RED_MUSHROOM, + Material.MELON_STEM, + Material.WATER_LILY, + Material.LONG_GRASS, + Material.FIRE, + Material.DEAD_BUSH, + // + Material.CROPS, + }; + + static{ + try{ + initTools(); + initBlocks(); + } + catch(Throwable t){ + t.printStackTrace(); + } + } + + private static void initTools() { + tools.put(268, new ToolProps(ToolType.SWORD, MaterialBase.WOOD)); + tools.put(269, new ToolProps(ToolType.SPADE, MaterialBase.WOOD)); + tools.put(270, new ToolProps(ToolType.PICKAXE, MaterialBase.WOOD)); + tools.put(271, new ToolProps(ToolType.AXE, MaterialBase.WOOD)); + + tools.put(272, new ToolProps(ToolType.SWORD, MaterialBase.STONE)); + tools.put(273, new ToolProps(ToolType.SPADE, MaterialBase.STONE)); + tools.put(274, new ToolProps(ToolType.PICKAXE, MaterialBase.STONE)); + tools.put(275, new ToolProps(ToolType.AXE, MaterialBase.STONE)); + + tools.put(256, new ToolProps(ToolType.SPADE, MaterialBase.IRON)); + tools.put(257, new ToolProps(ToolType.PICKAXE, MaterialBase.IRON)); + tools.put(258, new ToolProps(ToolType.AXE, MaterialBase.IRON)); + tools.put(267, new ToolProps(ToolType.SWORD, MaterialBase.IRON)); + + tools.put(276, new ToolProps(ToolType.SWORD, MaterialBase.DIAMOND)); + tools.put(277, new ToolProps(ToolType.SPADE, MaterialBase.DIAMOND)); + tools.put(278, new ToolProps(ToolType.PICKAXE, MaterialBase.DIAMOND)); + tools.put(279, new ToolProps(ToolType.AXE, MaterialBase.DIAMOND)); + + tools.put(283, new ToolProps(ToolType.SWORD, MaterialBase.GOLD)); + tools.put(284, new ToolProps(ToolType.SPADE, MaterialBase.GOLD)); + tools.put(285, new ToolProps(ToolType.PICKAXE, MaterialBase.GOLD)); + tools.put(286, new ToolProps(ToolType.AXE, MaterialBase.GOLD)); + + tools.put(359, new ToolProps(ToolType.SHEARS, MaterialBase.NONE)); + } + + + private static void initBlocks() { + for (int i = 0; i missing = new LinkedList(); + if (all) { + System.out.println("[NoCheatPlus] Dump block properties for fastbreak check:"); + System.out.println("--- Present entries -------------------------------"); + } + for (int i = 0; i < blocks.length; i++){ + String mat; + try{ + Material temp = Material.getMaterial(i); + if (!temp.isBlock()) continue; + mat = temp.toString(); + } + catch(Exception e){ + mat = "?"; + } + if (blocks[i] == null){ + if (mat.equals("?")) continue; + missing.add("* MISSING "+i + "(" + mat +") "); + } + else if (all) System.out.println(i + ": (" + mat + ") " + blocks[i].toString()); + } + if (!missing.isEmpty()){ + Bukkit.getLogger().warning("[NoCheatPlus] The block breaking data is incomplete, interpret some as stone :"); + System.out.println("--- Missing entries -------------------------------"); + for (String spec : missing){ + System.out.println(spec); + } + } + } + + + public static long[] secToMs(final double s1, final double s2, final double s3, final double s4, final double s5, final double s6){ + return new long[] { (long) (s1 * 1000d), (long) (s2 * 1000d), (long) (s3 * 1000d), (long) (s4 * 1000d), (long) (s5 * 1000d), (long) (s6 * 1000d) }; + } + + public static long[] secToMs(final double s1){ + final long v = (long) (s1 * 1000d); + return new long[]{v, v, v, v, v, v}; + } + + public static ToolProps getToolProps(final Material mat){ + if (mat == null) return noTool; + else return getToolProps(mat.getId()); + } + + public static ToolProps getToolProps(final Integer id){ + final ToolProps props = tools.get(id); + if (props == null) return noTool; + else return props; + } + + public static BlockProps getBlockProps(final int blockId){ + if (blockId <0 || blockId >= blocks.length || blocks[blockId] == null) return defaultBlockProps; + else return blocks[blockId]; + } + + /** + * Convenience method. + * @param blockId + * @param player + * @return + */ + public static long getBreakingDuration(final int blockId, final Player player){ + return getBreakingDuration(blockId, player.getItemInHand(), player.getInventory().getHelmet(), player, player.getLocation()); + } + + /** + * TODO: repair signature some day (rid of PlayerLocation). + * @param BlockId + * @param itemInHand May be null. + * @param helmet May be null. + * @param location The normal location of a player. + * @return + */ + public static long getBreakingDuration(final int blockId, final ItemStack itemInHand, final ItemStack helmet, final Player player, final Location location){ + final int x = location.getBlockX(); + final int y = location.getBlockY(); + final int z = location.getBlockZ(); + final World world = location.getWorld(); + final boolean onGround = isOnGround(player, location) || world.getBlockTypeIdAt(x, y, z) == Material.WATER_LILY.getId(); + final boolean inWater = isInWater(world.getBlockTypeIdAt(x, y + 1, z)); + return getBreakingDuration(blockId, itemInHand, onGround, inWater, helmet != null && helmet.containsEnchantment(Enchantment.WATER_WORKER)); + } + + public static boolean isInWater(final int blockId) { + if (blockId == Material.STATIONARY_WATER.getId() || blockId == Material.STATIONARY_LAVA.getId()) return true; + // TODO: count in water height ? + // TODO: lava ? + return false; + } + + + /** + * Heavy but ... + * @param world + * @param x + * @param y + * @param z + * @return + */ + public static boolean isOnGround(Player player, Location location) { +// return blockId != 0 && net.minecraft.server.Block.byId[blockId].//.c();// d(); + final PlayerLocation loc = new PlayerLocation(); + // Bit fat workaround, maybe put the object through from check listener ? + loc.set(location, player); + return loc.isOnGround(); + } + + /** * Get the normal breaking duration, including enchantments, and tool properties. * @param blockId * @param itemInHand * @return */ - public static long getBreakingDuration(final int blockId, final ItemStack itemInHand){ - // TODO: GET EXACT BREAKING TIME ! - return 95; + public static long getBreakingDuration(final int blockId, final ItemStack itemInHand, final boolean onGround, final boolean inWater, final boolean aquaAffinity){ + // TODO: more configurability / load from file for blocks (i.e. set for shears etc. + if (itemInHand == null) return getBreakingDuration(blockId, getBlockProps(blockId), noTool, onGround, inWater, aquaAffinity, 0); + else{ + int efficiency = 0; + if (itemInHand.containsEnchantment(Enchantment.DIG_SPEED)) efficiency = itemInHand.getEnchantmentLevel(Enchantment.DIG_SPEED); + return getBreakingDuration(blockId, getBlockProps(blockId), getToolProps(itemInHand.getTypeId()), onGround, inWater, aquaAffinity, efficiency); + } } + + public static long getBreakingDuration(final int blockId, final BlockProps blockProps, final ToolProps toolProps, final boolean onGround, final boolean inWater, boolean aquaAffinity, int efficiency) { + long duration; + + boolean isValidTool = blockProps.tool.toolType == toolProps.toolType; + + if (!isValidTool && efficiency > 0){ + // Efficiency makes the tool. + // (wood, sand, gravel, ice) + if (blockProps.hardness <= 2 && (blockProps.tool.toolType == ToolType.AXE || blockProps.tool.toolType == ToolType.SPADE + || (blockProps.hardness < 0.8 + && (blockId != Material.NETHERRACK.getId() && blockId != Material.SNOW.getId() && blockId != Material.SNOW_BLOCK.getId() && blockId != Material.STONE_PLATE.getId())))){ + // Also roughly. + isValidTool = true; + } + } + + if (isValidTool){ + // appropriate tool + duration = blockProps.breakingTimes[toolProps.materialBase.index]; + } + else{ + // Inappropriate tool. + duration = blockProps.breakingTimes[0]; + // Swords are always appropriate. + if (toolProps.toolType == ToolType.SWORD) duration = (long) ((float) duration / 1.5f); + } + + // Specialties: + if (toolProps.toolType == ToolType.SHEARS){ + // (Note: shears are not in the block props, anywhere) + // Treat these extra (party experimental): + if (blockId == Material.WEB.getId()){ + duration = 400; + isValidTool = true; + } + else if (blockId == Material.WOOL.getId()){ + duration = 240; + isValidTool = true; + } + else if (blockId == Material.LEAVES.getId()){ + duration = 20; + isValidTool = true; + } + else if (blockId == Material.VINE.getId()){ + duration = 300; + isValidTool = true; + } + } + // (sword vs web already counted) + + if (isValidTool || blockProps.tool.toolType == ToolType.NONE){ + if (inWater && ! aquaAffinity) + duration *= 5; + if (!onGround) + duration *= 5; + // Efficiency level. + if (efficiency > 0){ + // This seems roughly correct. + for (int i = 0; i < efficiency; i++){ + duration /= 1.33; // Matches well with obsidian. + } + } + } + return duration; + } + + public static void read(){ + + } + + } diff --git a/src/fr/neatmonster/nocheatplus/utilities/Stats.java b/src/fr/neatmonster/nocheatplus/utilities/Stats.java new file mode 100644 index 00000000..abc33700 --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/utilities/Stats.java @@ -0,0 +1,186 @@ +package fr.neatmonster.nocheatplus.utilities; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.ChatColor; + +/** + * A not too fat stats class re-used from other plugins. + * @author asofold + * + */ +public final class Stats { + + public static final class Entry{ + public long val = 0; + public long n = 0; + public long min = Long.MAX_VALUE; + public long max = Long.MIN_VALUE; + } + + private long tsStats = 0; + private long periodStats = 12345; + private long nVerbose = 500; + private long nDone = 0; + private boolean logStats = false; + private boolean showRange = true; + + private final Map entries = new HashMap(); + private final DecimalFormat f; + private final String label; + + /** + * Map id to name. + */ + private final Map idKeyMap = new HashMap(); + + /** + * Map exact name to id. + */ + private final Map keyIdMap = new HashMap(); + + int maxId = 0; + + public Stats(){ + this("[STATS]"); + } + + public Stats(final String label){ + this.label = label; + f = new DecimalFormat(); + f.setGroupingUsed(true); + f.setGroupingSize(3); + DecimalFormatSymbols s = f.getDecimalFormatSymbols(); + s.setGroupingSeparator(','); + f.setDecimalFormatSymbols(s); + } + + public final void addStats(final Integer key, final long value){ + Entry entry = entries.get(key); + if ( entry != null){ + entry.n += 1; + entry.val += value; + if (value < entry.min) entry.min = value; + else if (value > entry.max) entry.max = value; + } else{ + entry = new Entry(); + entry.val = value; + entry.n = 1; + entries.put(key, entry); + entry.min = value; + entry.max = value; + } + if (!logStats) return; + nDone++; + if ( nDone>nVerbose){ + nDone = 0; + long ts = System.currentTimeMillis(); + if ( ts > tsStats+periodStats){ + tsStats = ts; + // print out stats ! + System.out.println(getStatsStr()); + } + } + } + + /** + * Get stats representation without ChatColor. + * @return + */ + public final String getStatsStr() { + return getStatsStr(false); + } + + public final String getStatsStr(final boolean colors) { + final StringBuilder b = new StringBuilder(400); + b.append(label+" "); + boolean first = true; + for (final Integer id : entries.keySet()){ + if ( !first) b.append(" | "); + final Entry entry = entries.get(id); + String av = f.format(entry.val / entry.n); + String key = getKey(id); + String n = f.format(entry.n); + if (colors){ + key = ChatColor.GREEN + key + ChatColor.WHITE; + n = ChatColor.AQUA + n + ChatColor.WHITE; + av = ChatColor.YELLOW + av + ChatColor.WHITE; + } + b.append(key+" av="+av+" n="+n); + if ( showRange) b.append(" rg="+f.format(entry.min)+"..."+f.format(entry.max)); + first = false; + } + return b.toString(); + } + + /** + * Always returns some string, if not key is there, stating that no key is there. + * @param id + * @return + */ + public final String getKey(final Integer id) { + String key = idKeyMap.get(id); + if (key == null){ + key = ""; + idKeyMap.put(id, key); + keyIdMap.put(key, id); + } + return key; + + } + + /** + * Get a new id for the key. + * @param key + * @return + */ + public final Integer getNewId(final String key){ + maxId++; + while (idKeyMap.containsKey(maxId)){ + maxId++; // probably not going to happen... + } + idKeyMap.put(maxId, key); + keyIdMap.put(key, maxId); + return maxId; + } + + /** + * + * @param key + * @param create if to create a key - id mapping if not existent. + * @return + */ + public final Integer getId(final String key, final boolean create){ + final Integer id = keyIdMap.get(key); + if (id == null){ + if (create) return getNewId(key); + else return null; + } + else return id; + } + + /** + * Gets the id if present, returns null otherwise. + * @param key not null + * @return Key or null. + */ + public final Integer getId(final String key){ + return keyIdMap.get(key); + } + + public final void clear(){ + entries.clear(); + } + + public final void setLogStats(final boolean log){ + logStats = log; + } + + public final void setShowRange(final boolean set){ + showRange = set; + } + +}