diff --git a/src/fr/neatmonster/nocheatplus/checks/CheckType.java b/src/fr/neatmonster/nocheatplus/checks/CheckType.java index fe883740..9b052585 100644 --- a/src/fr/neatmonster/nocheatplus/checks/CheckType.java +++ b/src/fr/neatmonster/nocheatplus/checks/CheckType.java @@ -54,6 +54,7 @@ public enum CheckType { CHAT(ChatConfig.factory, ChatData.factory), CHAT_COLOR(CHAT, Permissions.CHAT_COLOR), CHAT_NOPWNAGE(CHAT, Permissions.CHAT_NOPWNAGE), + CHAT_GLOBALCHAT(CHAT, Permissions.CHAT_GLOBALCHAT), FIGHT(FightConfig.factory, FightData.factory), FIGHT_ANGLE(FIGHT, Permissions.FIGHT_ANGLE), diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java b/src/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java index 5bdc7815..b3043028 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java @@ -69,6 +69,12 @@ public class ChatConfig implements CheckConfig { public final boolean colorCheck; public final ActionList colorActions; + + public final boolean globalChatCheck; + public double globalChatFrequencyFactor; + public final double globalChatFrequencyWeight; + public final double globalChatLevel; + public final ActionList globalChatActions; public final boolean noPwnageCheck; public final List noPwnageExclusions; @@ -135,6 +141,12 @@ public class ChatConfig implements CheckConfig { public ChatConfig(final ConfigFile data) { colorCheck = data.getBoolean(ConfPaths.CHAT_COLOR_CHECK); colorActions = data.getActionList(ConfPaths.CHAT_COLOR_ACTIONS, Permissions.CHAT_COLOR); + + globalChatCheck = data.getBoolean(ConfPaths.CHAT_GLOBALCHAT_CHECK); + globalChatFrequencyFactor = data.getDouble(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_FACTOR); + globalChatFrequencyWeight = data.getDouble(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_WEIGHT); + globalChatLevel = data.getDouble(ConfPaths.CHAT_GLOBALCHAT_LEVEL); + globalChatActions = data.getActionList(ConfPaths.CHAT_GLOBALCHAT_ACTIONS, Permissions.CHAT_GLOBALCHAT); noPwnageCheck = data.getBoolean(ConfPaths.CHAT_NOPWNAGE_CHECK); noPwnageExclusions = data.getStringList(ConfPaths.CHAT_NOPWNAGE_EXCLUSIONS); @@ -201,6 +213,8 @@ public class ChatConfig implements CheckConfig { switch (checkType) { case CHAT_COLOR: return colorCheck; + case CHAT_GLOBALCHAT: + return globalChatCheck; case CHAT_NOPWNAGE: return noPwnageCheck; default: diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/ChatData.java b/src/fr/neatmonster/nocheatplus/checks/chat/ChatData.java index 2230fe7a..b0633fd5 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/ChatData.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/ChatData.java @@ -7,6 +7,7 @@ import org.bukkit.entity.Player; import fr.neatmonster.nocheatplus.checks.CheckData; import fr.neatmonster.nocheatplus.checks.CheckDataFactory; +import fr.neatmonster.nocheatplus.utilities.ActionFrequency; /* * MM'""""'YMM dP dP M""""""'YMM dP @@ -49,7 +50,11 @@ public class ChatData implements CheckData { // Violation levels. public double captchaVL; public double colorVL; + public double globalChatVL; public double noPwnageVL; + + // Data of the globalchat check. + public final ActionFrequency globalChatFrequency = new ActionFrequency(10, 3000); // Data of the no pwnage check. public int noPwnageCaptchTries; diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java b/src/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java index 9061818f..95834722 100644 --- a/src/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java +++ b/src/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java @@ -34,6 +34,9 @@ public class ChatListener implements Listener { /** The no pwnage check. */ private final NoPwnage noPwnage = new NoPwnage(); + + /** Global chat check (experiment: alternative / supplement). */ + private final GlobalChat globalChat = new GlobalChat(); /** * We listen to PlayerChat events for obvious reasons. @@ -60,7 +63,10 @@ public class ChatListener implements Listener { // Then the no pwnage check. if (noPwnage.check(player, event.getMessage(), false)) event.setCancelled(true); -// player.kickPlayer(CheckUtils.removeColors(ChatConfig.getConfig(player).noPwnageKickMessage)); + else if (globalChat.check(player, event.getMessage(), (ICaptcha) noPwnage)) + // Only check those that got through. + // (ICaptcha to start captcha if desired.) + event.setCancelled(true); } /** @@ -115,7 +121,6 @@ public class ChatListener implements Listener { // Then the no pwnage check. if (noPwnage.check(player, event.getMessage(), true)) event.setCancelled(true); -// player.kickPlayer(CheckUtils.removeColors(ChatConfig.getConfig(player).noPwnageKickMessage)); } /** diff --git a/src/fr/neatmonster/nocheatplus/checks/chat/GlobalChat.java b/src/fr/neatmonster/nocheatplus/checks/chat/GlobalChat.java new file mode 100644 index 00000000..c1e2c348 --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/checks/chat/GlobalChat.java @@ -0,0 +1,67 @@ +package fr.neatmonster.nocheatplus.checks.chat; + +import org.bukkit.entity.Player; + +import fr.neatmonster.nocheatplus.checks.Check; +import fr.neatmonster.nocheatplus.checks.CheckType; +import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager; +import fr.neatmonster.nocheatplus.players.Permissions; + +/** + * Some alternative more or less advanced analysis methods. + * @author mc_dev + * + */ +public class GlobalChat extends Check{ + + public GlobalChat() { + super(CheckType.CHAT_GLOBALCHAT); + } + + /** + * + * @param player + * @param message + * @param captcha Used for starting captcha on failure. + * @return + */ + public boolean check(final Player player, final String message, final ICaptcha captcha) { + // Take time once: + final long time = System.currentTimeMillis(); + + final ChatConfig cc = ChatConfig.getConfig(player); + + // Checking the player, actually. + if (!cc.isEnabled(type) || NCPExemptionManager.isExempted(player, type)) + return false; + + final ChatData data = ChatData.getData(player); + + boolean cancel = false; + + data.globalChatFrequency.add(time); + double score = cc.globalChatFrequencyWeight * data.globalChatFrequency.getScore(cc.globalChatFrequencyFactor); + if (score < 2.0 * cc.globalChatFrequencyWeight) + // Reset the VL. + data.globalChatVL = 0.0; + + // TODO Core checks.... + + if (score > cc.globalChatLevel){ + if (captcha.shouldStartCaptcha(cc, data)){ + captcha.sendNewCaptcha(player, cc, data); + cancel = true; + } + else{ + data.globalChatVL += score / 10.0; + if (executeActionsThreadSafe(player, data.globalChatVL, score, cc.globalChatActions, Permissions.CHAT_GLOBALCHAT)) + cancel = true; + } + } + else + data.globalChatVL *= 0.95; + + return cancel; + } + +} diff --git a/src/fr/neatmonster/nocheatplus/config/ConfPaths.java b/src/fr/neatmonster/nocheatplus/config/ConfPaths.java index 6ee30041..be424b35 100644 --- a/src/fr/neatmonster/nocheatplus/config/ConfPaths.java +++ b/src/fr/neatmonster/nocheatplus/config/ConfPaths.java @@ -138,6 +138,14 @@ public abstract class ConfPaths { private static final String CHAT_COLOR = CHAT + "color."; public static final String CHAT_COLOR_CHECK = CHAT_COLOR + "active"; public static final String CHAT_COLOR_ACTIONS = CHAT_COLOR + "actions"; + + public static final String CHAT_GLOBALCHAT = CHAT + "globalchat."; + public static final String CHAT_GLOBALCHAT_CHECK = CHAT_GLOBALCHAT + "active"; + public static final String CHAT_GLOBALCHAT_FREQUENCY = CHAT_GLOBALCHAT + "frequency."; + public static final String CHAT_GLOBALCHAT_FREQUENCY_WEIGHT = CHAT_GLOBALCHAT_FREQUENCY + "weight"; + public static final String CHAT_GLOBALCHAT_FREQUENCY_FACTOR = CHAT_GLOBALCHAT_FREQUENCY + "factor"; + public static final String CHAT_GLOBALCHAT_LEVEL = CHAT_GLOBALCHAT + "level"; + public static final String CHAT_GLOBALCHAT_ACTIONS = CHAT_GLOBALCHAT + "actions"; private static final String CHAT_NOPWNAGE = CHAT + "nopwnage."; public static final String CHAT_NOPWNAGE_CHECK = CHAT_NOPWNAGE + "active"; diff --git a/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java b/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java index 33ed213a..60924a62 100644 --- a/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java +++ b/src/fr/neatmonster/nocheatplus/config/DefaultConfig.java @@ -124,6 +124,12 @@ public class DefaultConfig extends ConfigFile { */ set(ConfPaths.CHAT_COLOR_CHECK, true); set(ConfPaths.CHAT_COLOR_ACTIONS, "log:color:0:1:if cancel"); + + set(ConfPaths.CHAT_GLOBALCHAT_CHECK, true); + set(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_FACTOR, 0.8D); + set(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_WEIGHT, 6.0D); + set(ConfPaths.CHAT_GLOBALCHAT_LEVEL, 30D); + set(ConfPaths.CHAT_GLOBALCHAT_ACTIONS, "log:globalchat:0:5:if cancel"); set(ConfPaths.CHAT_NOPWNAGE_CHECK, true); set(ConfPaths.CHAT_NOPWNAGE_EXCLUSIONS, new ArrayList()); @@ -318,6 +324,7 @@ public class DefaultConfig extends ConfigFile { + "tried to move from [locationfrom] to [locationto] over a distance of [distance] block(s)" + end); set(ConfPaths.STRINGS + ".freach", start + "tried to attack entity out of reach" + end); set(ConfPaths.STRINGS + ".fspeed", start + "tried to attack more than [limit] times per second" + end); + set(ConfPaths.STRINGS + ".globalchat", start + "potentially annoying chat" + end); set(ConfPaths.STRINGS + ".godmode", start + "avoided taking damage or lagging" + end); set(ConfPaths.STRINGS + ".instantbow", start + "fires bow to fast" + end); set(ConfPaths.STRINGS + ".instanteat", start + "eats food [food] too fast" + end); diff --git a/src/fr/neatmonster/nocheatplus/players/Permissions.java b/src/fr/neatmonster/nocheatplus/players/Permissions.java index 1dc29397..a1ed978d 100644 --- a/src/fr/neatmonster/nocheatplus/players/Permissions.java +++ b/src/fr/neatmonster/nocheatplus/players/Permissions.java @@ -81,6 +81,7 @@ public class Permissions { */ private static final String CHAT = CHECKS + ".chat"; public static final String CHAT_COLOR = CHAT + ".color"; + public static final String CHAT_GLOBALCHAT = CHAT + ".globalchat"; public static final String CHAT_NOPWNAGE = CHAT + ".nopwnage"; public static final String CHAT_NOPWNAGE_CAPTCHA = CHAT_NOPWNAGE + ".captcha"; diff --git a/src/fr/neatmonster/nocheatplus/utilities/ActionFrequency.java b/src/fr/neatmonster/nocheatplus/utilities/ActionFrequency.java new file mode 100644 index 00000000..389dd551 --- /dev/null +++ b/src/fr/neatmonster/nocheatplus/utilities/ActionFrequency.java @@ -0,0 +1,85 @@ +package fr.neatmonster.nocheatplus.utilities; + +/** + * Keep track of frequency of some action, + * sort into buckets, representing time intervals, + * TODO: find a better name. + * @author mc_dev + * + */ +public class ActionFrequency { + + /** Reference time for filling in. */ + long time = 0; + + final int[] buckets; + + final long durBucket; + + public ActionFrequency(final int nBuckets, final long durBucket){ + this.buckets = new int[nBuckets]; + this.durBucket = durBucket; + } + + /** + * Update and add. + * @param ts + */ + public void add(final long now){ + update(now); + } + + /** + * Update without adding, also updates time. + * @param now + */ + public void update(final long now) { + final long diff = now - time; + final int shift = (int) ((float) diff / (float) durBucket); + if (shift == 0){ + // No update, just fill in. + buckets[0] ++; + return; + } + else if (shift >= buckets.length){ + // Clear and fill in (beyond range). + clear(now); + buckets[0] ++; + return; + } + // Update buckets. + for (int i = 0; i < buckets.length - shift; i++){ + buckets[buckets.length - (i + 1)] = buckets[buckets.length - (i + 1 + shift)]; + } + for (int i = 0; i < shift; i++){ + buckets[i] = 0; + } + buckets[0] ++; + // Set time according to bucket duration (!). + time += durBucket * shift; + } + + public void clear(final long now) { + for (int i = 0; i < buckets.length; i++){ + buckets[i] = 0; + } + time = now; + } + + + /** + * Get a weighted sum score, weight for bucket i: w(i) = factor^i. + * @param factor + * @return + */ + public double getScore(final double factor){ + double res = buckets[0]; + double cf = factor; + for (int i = 1; i < buckets.length; i++){ + res += cf * buckets[i]; + cf *= factor; + } + return res; + } + +}