Re-structure globalchat: rename config paths, add hidden settings, add

similarity check. Adjust default captcha letters (Ticket 194).
This commit is contained in:
asofold 2012-09-07 10:31:07 +02:00
parent 89a5b3221f
commit d4103899a5
21 changed files with 1202 additions and 235 deletions

View File

@ -71,11 +71,14 @@ public class ChatConfig implements CheckConfig {
public final ActionList colorActions;
public final boolean globalChatCheck;
public final boolean globalChatEngineCheck;
public final EnginePlayerConfig globalChatEnginePlayerConfig;
public final float globalChatFrequencyFactor;
public final float globalChatFrequencyWeight;
public final float globalChatGlobalWeight;
public final float globalChatPlayerWeight;
public final double globalChatLevel;
public boolean globalChatEngineMaximum;
public final boolean globalChatDebug;
public final ActionList globalChatActions;
public final boolean noPwnageCheck;
@ -145,11 +148,14 @@ public class ChatConfig implements CheckConfig {
colorActions = config.getActionList(ConfPaths.CHAT_COLOR_ACTIONS, Permissions.CHAT_COLOR);
globalChatCheck = config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_CHECK);
globalChatEngineCheck = config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_ENGINE_CHECK);
globalChatEnginePlayerConfig = new EnginePlayerConfig(config);
globalChatFrequencyFactor = (float) config.getDouble(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_FACTOR);
globalChatFrequencyWeight = (float) config.getDouble(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_WEIGHT);
globalChatGlobalWeight = (float) config.getDouble(ConfPaths.CHAT_GLOBALCHAT_GL_WEIGHT, 1.0);
globalChatPlayerWeight = (float) config.getDouble(ConfPaths.CHAT_GLOBALCHAT_PP_WEIGHT, 1.0);
globalChatLevel = config.getDouble(ConfPaths.CHAT_GLOBALCHAT_LEVEL);
globalChatEngineMaximum = config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_ENGINE_MAXIMUM, true);
globalChatDebug = config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_DEBUG, false);
globalChatActions = config.getActionList(ConfPaths.CHAT_GLOBALCHAT_ACTIONS, Permissions.CHAT_GLOBALCHAT);
noPwnageCheck = config.getBoolean(ConfPaths.CHAT_NOPWNAGE_CHECK);

View File

@ -1,5 +1,10 @@
package fr.neatmonster.nocheatplus.checks.chat;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.checks.Check;
@ -11,6 +16,7 @@ import fr.neatmonster.nocheatplus.command.INotifyReload;
import fr.neatmonster.nocheatplus.config.ConfigFile;
import fr.neatmonster.nocheatplus.config.ConfigManager;
import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;
import fr.neatmonster.nocheatplus.utilities.CheckUtils;
/**
* Some alternative more or less advanced analysis methods.
@ -48,7 +54,7 @@ public class GlobalChat extends Check implements INotifyReload{
synchronized (data) {
return unsafeCheck(player, message, captcha, cc, data, isMainThread);
}
}
}
private void init() {
@ -89,6 +95,15 @@ public class GlobalChat extends Check implements INotifyReload{
boolean cancel = false;
boolean debug = cc.globalChatDebug;
final List<String> debugParts;
if (debug){
debugParts = new LinkedList<String>();
debugParts.add("[NoCheatPlus][globalchat] Message ("+player.getName()+"/"+message.length()+"): ");
}
else debugParts = null;
// Update the frequency interval weights.
data.globalChatFrequency.update(time);
@ -152,22 +167,28 @@ public class GlobalChat extends Check implements INotifyReload{
wWords /= (float) letterCounts.words.length;
score += wWords;
if (debug && score > 0f) debugParts.add("Simple score: " + CheckUtils.fdec3.format(score));
// Engine:
if (cc.globalChatEngineCheck){
final float wEngine;
synchronized (engine) {
wEngine = engine.process(letterCounts, player.getName(), cc, data);
}
score += wEngine;
// TODO: more fine grained sync !
float wEngine = 0f;
final Map<String, Float> engMap;
synchronized (engine) {
engMap = engine.process(letterCounts, player.getName(), cc, data);
// TODO: more fine grained sync !s
// TODO: different methods (add or max or add+max or something else).
for (final Float res : engMap.values()){
if (cc.globalChatEngineMaximum) wEngine = Math.max(wEngine, res.floatValue());
else wEngine += res.floatValue();
}
}
score += wEngine;
// Wrapping it up. --------------------
// Add weight to frequency counts.
data.globalChatFrequency.add(time, score);
final float accumulated = cc.globalChatFrequencyWeight * data.globalChatFrequency.getScore(cc.globalChatFrequencyFactor);
// System.out.println("Total score: " + score + " (" + accumulated + ")");
if (score < 2.0f * cc.globalChatFrequencyWeight)
// Reset the VL.
data.globalChatVL = 0.0;
@ -185,7 +206,21 @@ public class GlobalChat extends Check implements INotifyReload{
}
else
data.globalChatVL *= 0.95;
if (debug) {
final List<String> keys = new LinkedList<String>(engMap.keySet());
Collections.sort(keys);
for (String key : keys) {
Float s = engMap.get(key);
if (s.floatValue() > 0.0f)
debugParts.add(key + ":" + CheckUtils.fdec3.format(s));
}
if (wEngine > 0.0f)
debugParts.add("Engine score (" + (cc.globalChatEngineMaximum?"max":"sum") + "): " + CheckUtils.fdec3.format(wEngine));
debugParts.add("Total score: " + CheckUtils.fdec3.format(score) + " (weigth=" + cc.globalChatFrequencyWeight + " => accumulated=" + CheckUtils.fdec3.format(accumulated) + ", vl=" + CheckUtils.fdec3.format(data.globalChatVL));
CheckUtils.scheduleOutputJoined(debugParts, " | ");
debugParts.clear();
}
return cancel;
}

View File

@ -0,0 +1,132 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.BKLevenshtein.LevenNode;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.BKModTree.LookupEntry;
/**
* Some version of a BK-Levenshtein tree.
* @author mc_dev
*
* @param <N>
*/
public class BKLevenshtein<N extends LevenNode<N>, L extends LookupEntry<char[], N>> extends BKModTree<char[], N, L> {
/**
* Fat default impl.
* @author mc_dev
*
* @param <N>
*/
public static class LevenNode<N extends LevenNode<N>> extends HashMapNode<char[], N>{
public LevenNode(char[] value) {
super(value);
}
}
public BKLevenshtein(NodeFactory<char[], N> nodeFactory, LookupEntryFactory<char[], N, L> resultFactory) {
super(nodeFactory, resultFactory);
}
// @Override
// public int distance(final char[] s1, final char[] s2) {
// /*
// * Levenshtein distance, also known as edit distance.
// * Not optimal implementation (m*n space).
// * Gusfield, Dan (1999), Algorithms on Strings, Sequences and Trees. Cambridge: University Press.
// * (2012/09/07) http://en.literateprograms.org/Levenshtein_distance_%28Java%29#chunk%20use:usage
// */
// final int n = s1.length;
// final int m = s2.length;
// if (n == m){
// // Bad style "fix", to return 0 on equality.
// int match = 0;
// for (int i = 0; i < n; i++){
// if (s1[i] == s2[i]) match ++;
// else break;
// }
// if (match == m) return 0;
// }
// final int[][] dp = new int[n + 1][m + 1];
// for (int i = 0; i < dp.length; i++) {
// for (int j = 0; j < dp[i].length; j++) {
// dp[i][j] = i == 0 ? j : j == 0 ? i : 0;
// if (i > 0 && j > 0) {
// if (s1[i - 1] == s2[j - 1])
// dp[i][j] = dp[i - 1][j - 1];
// else
// dp[i][j] = Math.min(dp[i][j - 1] + 1, Math.min(
// dp[i - 1][j - 1] + 1, dp[i - 1][j] + 1));
// }
// }
// }
// return dp[n][m];
// }
@Override
public int distance(char[] s, char[] t) {
/*
* Adapted from CheckUtils to char[].
* NOTE: RETURNS 1 FOR SAME STRINGS.
*/
// if (s == null || t == null)
// throw new IllegalArgumentException("Strings must not be null");
int n = s.length;
int m = t.length;
if (n == m){
// Return equality faster.
for (int i = 0; i < n; i++){
if (s[i] == t[i]) m --;
else break;
}
if (m == 0) return 0;
m = n; // Reset.
}
if (n == 0)
return m;
else if (m == 0)
return n;
if (n > m) {
final char[] tmp = s;
s = t;
t = tmp;
n = m;
m = t.length;
}
int p[] = new int[n + 1];
int d[] = new int[n + 1];
int _d[];
int i;
int j;
char t_j;
int cost;
for (i = 0; i <= n; i++)
p[i] = i;
for (j = 1; j <= m; j++) {
t_j = t[j - 1];
d[0] = j;
for (i = 1; i <= n; i++) {
cost = s[i - 1] == t_j ? 0 : 1;
d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1]
+ cost);
}
_d = p;
p = d;
d = _d;
}
return p[n];
}
}

View File

@ -0,0 +1,271 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.BKModTree.LookupEntry;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.BKModTree.Node;
/**
* BK tree for int distances.
* @author mc_dev
*
*/
public abstract class BKModTree<V, N extends Node<V, N>, L extends LookupEntry<V, N>>{
// TODO: Support for other value (equals) than used for lookup (distance).
// TODO: What with dist = 0 -> support for exact hit !
/**
* Fat defaultimpl. it iterates over all Children
* @author mc_dev
*
* @param <V>
* @param <N>
*/
public static abstract class Node<V, N extends Node<V, N>>{
public V value;
public Node(V value){
this.value = value;
}
public abstract N putChild(final int distance, final N child);
public abstract N getChild(final int distance);
public abstract boolean hasChild(int distance);
public abstract Collection<N> getChildren(final int distance, final int range, final Collection<N> nodes);
}
/**
* Node using a map as base, with basic implementation.
* @author mc_dev
*
* @param <V>
* @param <N>
*/
public static abstract class MapNode<V, N extends HashMapNode<V, N>> extends Node<V, N>{
protected Map<Integer, N> children = null; // Only created if needed.
protected int maxIterate = 12; // Maybe add a setter.
public MapNode(V value) {
super(value);
}
@Override
public N putChild(final int distance, final N child){
if (children == null) children = newMap();
children.put(distance, child);
return child;
}
@Override
public N getChild(final int distance){
if (children == null) return null;
return children.get(distance);
}
@Override
public boolean hasChild(int distance) {
if (children == null) return false;
return children.containsKey(distance);
}
@Override
public Collection<N> getChildren(final int distance, final int range, final Collection<N> nodes){
if (children == null) return nodes;
// TODO: maybe just go for iterating till range (from 0 on) to have closest first (no keyset).
if (children.size() > maxIterate){
for (int i = distance - range; i < distance + range + 1; i ++){
final N child = children.get(i);
if (child != null) nodes.add(child);
}
}
else{
for (final Integer key : children.keySet()){
// TODO: Not sure this is faster than the EntrySet in average.
if (Math.abs(distance - key.intValue()) <= range) nodes.add(children.get(key));
}
}
return nodes;
}
/**
* Map factory method.
* @return
*/
protected abstract Map<Integer, N> newMap();
}
/**
* Node using a simple HashMap.
* @author mc_dev
*
* @param <V>
* @param <N>
*/
public static class HashMapNode<V, N extends HashMapNode<V, N>> extends MapNode<V, N>{
/** Map Levenshtein distance to next nodes. */
protected int initialCapacity = 4;
protected float loadFactor = 0.75f;
public HashMapNode(V value) {
super(value);
}
@Override
protected Map<Integer, N> newMap() {
return new HashMap<Integer, N>(initialCapacity, loadFactor);
}
}
public static class SimpleNode<V> extends HashMapNode<V, SimpleNode<V>>{
public SimpleNode(V content) {
super(content);
}
}
public static interface NodeFactory<V, N extends Node<V, N>>{
public N newNode(V value, N parent);
}
/**
* Result of a lookup.
* @author mc_dev
*
* @param <V>
* @param <N>
*/
public static class LookupEntry<V, N extends Node<V, N>>{
// TODO: What nodes are in nodes, actually? Those from the way that were in range ?
// TODO: This way one does not know which distance a node has. [subject to changes]
// TODO: Add depth and some useful info ?
/** All visited nodes within range of distance. */
public final Collection<N> nodes;
/** Matching node */
public final N match;
/** Distance from value to match.value */
public final int distance;
/** If the node match is newly inserted.*/
public final boolean isNew;
public LookupEntry(Collection<N> nodes, N match, int distance, boolean isNew){
this.nodes = nodes;
this.match = match;
this.distance = distance;
this.isNew = isNew;
}
}
public static interface LookupEntryFactory<V, N extends Node<V, N>, L extends LookupEntry<V, N>>{
public L newLookupEntry(Collection<N> nodes, N match, int distance, boolean isNew);
}
protected final NodeFactory<V, N> nodeFactory;
protected final LookupEntryFactory<V, N, L> resultFactory;
protected N root = null;
/** Set to true to have visit called */
protected boolean visit = false;
public BKModTree(NodeFactory<V, N> nodeFactory, LookupEntryFactory<V, N, L> resultFactory){
this.nodeFactory = nodeFactory;
this.resultFactory = resultFactory;
}
public void clear(){
root = null;
}
/**
*
* @param value
* @param range Maximum difference from distance of node.value to children.
* @param seekMax If node.value is within distance but not matching, this is the maximum number of steps to search on.
* @param create
* @return
*/
public L lookup(final V value, final int range, final int seekMax, final boolean create){ // TODO: signature.
final List<N> inRange = new LinkedList<N>();
if (root == null){
if (create){
root = nodeFactory.newNode(value, null);
return resultFactory.newLookupEntry(inRange, root, 0, true);
}
else{
return resultFactory.newLookupEntry(inRange, null, 0, false);
}
}
// TODO: best queue type.
final List<N> open = new ArrayList<N>();
open.add(root);
N insertion = null;
int insertionDist = 0;
do{
final N current = open.remove(open.size() - 1);
int distance = distance(current.value, value);
if (visit) visit(current, value, distance);
if (distance == 0){
// exact match.
return resultFactory.newLookupEntry(inRange, current, distance, false);
}
// Set node as insertion point.
if (create && insertion == null && !current.hasChild(distance)){
insertion = current;
insertionDist = distance;
// TODO: use
}
// Within range ?
if (Math.abs(distance) <= range){
inRange.add(current);
// Check special abort conditions.
if (seekMax > 0 && inRange.size() >= seekMax){
// TODO: Keep this ?
// Break if insertion point is found, or not needed.
if (!create || insertion != null){
break;
}
}
}
// Continue search with children.
current.getChildren(distance, range, open);
// TODO: deterministic: always same node visited for the same value ? [Not with children = HashMap...]
} while (!open.isEmpty());
// TODO: is the result supposed to be the closest match, if any ?
if (create && insertion != null){
final N newNode = nodeFactory.newNode(value, insertion);
insertion.putChild(insertionDist, newNode);
return resultFactory.newLookupEntry(inRange, newNode, 0, true);
}
else{
return resultFactory.newLookupEntry(inRange, null, 0, false);
}
}
/**
* Visit a node during lookup.
* @param node
* @param distance
* @param value
*/
protected void visit(N node, V value, int distance){
// Override if needed.
}
//////////////////////////////////////////////
// Abstract methods.
//////////////////////////////////////////////
/**
* Calculate the distance of two values.
* @param v1
* @param v2
* @return
*/
public abstract int distance(V v1, V v2);
}

View File

@ -0,0 +1,35 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree;
import java.util.Collection;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.BKModTree.LookupEntry;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.SimpleTimedBKLevenshtein.STBKLResult;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.TimedBKLevenshtein.SimpleTimedLevenNode;
public class SimpleTimedBKLevenshtein extends TimedBKLevenshtein<SimpleTimedLevenNode, STBKLResult> {
public static class STBKLResult extends LookupEntry<char[], SimpleTimedLevenNode>{
public STBKLResult(Collection<SimpleTimedLevenNode> nodes, SimpleTimedLevenNode match, int distance, boolean isNew) {
super(nodes, match, distance, isNew);
}
}
public SimpleTimedBKLevenshtein() {
super(
new NodeFactory<char[], SimpleTimedLevenNode>(){
@Override
public SimpleTimedLevenNode newNode( char[] value, SimpleTimedLevenNode parent) {
return new SimpleTimedLevenNode(value);
}
}
,
new LookupEntryFactory<char[], SimpleTimedLevenNode, STBKLResult>() {
@Override
public STBKLResult newLookupEntry(Collection<SimpleTimedLevenNode> nodes, SimpleTimedLevenNode match, int distance, boolean isNew) {
return new STBKLResult(nodes, match, distance, isNew);
}
}
);
}
}

View File

@ -0,0 +1,38 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.BKModTree.LookupEntry;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.TimedBKLevenshtein.TimedLevenNode;
public class TimedBKLevenshtein<N extends TimedLevenNode<N>, L extends LookupEntry<char[], N>> extends BKLevenshtein<N, L> {
public static class TimedLevenNode<N extends TimedLevenNode<N>> extends LevenNode<N>{
public long ts;
/**
* Set time to now.
* @param value
*/
public TimedLevenNode(char[] value) {
super(value);
this.ts = System.currentTimeMillis();
}
public TimedLevenNode(char[] value, long ts){
super(value);
this.ts = ts;
}
}
public static class SimpleTimedLevenNode extends TimedLevenNode<SimpleTimedLevenNode>{
public SimpleTimedLevenNode(char[] value) {
super(value);
}
public SimpleTimedLevenNode(char[] value, long ts){
super(value, ts);
}
}
public TimedBKLevenshtein(NodeFactory<char[], N> nodeFactory, LookupEntryFactory<char[], N, L> resultFactory) {
super(nodeFactory, resultFactory);
}
}

View File

@ -1,99 +0,0 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import fr.neatmonster.nocheatplus.checks.chat.analysis.MessageLetterCount;
import fr.neatmonster.nocheatplus.checks.chat.analysis.WordLetterCount;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.prefixtree.SimpleTimedCharPrefixTree;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.prefixtree.SimpleTimedCharPrefixTree.SimpleTimedCharLookupEntry;
public class CompressedWords extends AbstractWordProcessor{
protected final SimpleTimedCharPrefixTree tree = new SimpleTimedCharPrefixTree(true);
protected final int maxAdd;
protected int added = 0;
protected final boolean sort;
protected final long durExpire;
protected final List<Character> letters = new ArrayList<Character>(10);
protected final List<Character> digits = new ArrayList<Character>(10);
protected final List<Character> other = new ArrayList<Character>(10);
public CompressedWords(long durExpire, int maxAdd, boolean sort) {
super("CompressedWords");
this.durExpire = durExpire;
this.maxAdd = maxAdd;
this.sort = sort;
}
@Override
public void start(final MessageLetterCount message) {
// This allows adding up to maximum messge length more characters,
// but also allows to set size of nodes exactly.
// TODO: Some better method than blunt clear (extra LinkedHashSet/LRU?).
if (added > maxAdd) tree.clear();
added = 0;
}
@Override
public float loop(final long ts, final int index, final String key, final WordLetterCount word) {
final int len = word.counts.size();
letters.clear();
digits.clear();
other.clear();
for (Character c : word.counts.keySet()){
if (Character.isLetter(c)) letters.add(c);
else if (Character.isDigit(c)) digits.add(c);
else other.add(c);
}
if (sort){
Collections.sort(letters);
Collections.sort(digits);
Collections.sort(other);
}
float score = 0;
if (!letters.isEmpty()){
score += getScore(letters, ts);
}
if (!digits.isEmpty()){
score += getScore(digits, ts);
}
if (!other.isEmpty()){
score += getScore(other, ts);
}
return word.counts.isEmpty()?0f:(score / (float) len);
}
@Override
public void clear() {
tree.clear();
letters.clear();
digits.clear();
other.clear();
}
protected float getScore(final List<Character> chars, final long ts) {
final int len = chars.size();
final SimpleTimedCharLookupEntry entry = tree.lookup(chars, true);
final int depth = entry.depth;
float score = 0f;
for (int i = 0; i < depth ; i++){
final long age = ts - entry.timeInsertion[i];
if (age < durExpire)
score += 1f / (float) (depth - i) * (float) (durExpire - age) / (float) durExpire;
}
if (depth == len){
score += 0.2;
if (entry.insertion.isEnd) score += 0.2;
}
if (len != depth) added += len - depth;
return score;
}
}

View File

@ -1,5 +1,8 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.FlatWords.FlatWordsSettings;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.SimilarWordsBKL.SimilarWordsBKLSettings;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.WordPrefixes.WordPrefixesSettings;
import fr.neatmonster.nocheatplus.config.ConfPaths;
import fr.neatmonster.nocheatplus.config.ConfigFile;
@ -10,11 +13,35 @@ import fr.neatmonster.nocheatplus.config.ConfigFile;
*/
public class EnginePlayerConfig {
public final boolean ppComprWordsCheck;
public final boolean ppWordFrequencyCheck;
public final boolean ppPrefixesCheck;
public final WordPrefixesSettings ppPrefixesSettings;
public final boolean ppWordsCheck;
public final FlatWordsSettings ppWordsSettings;
public final boolean ppSimilarityCheck;
public final SimilarWordsBKLSettings ppSimilaritySettings;
public EnginePlayerConfig(final ConfigFile config){
ppWordFrequencyCheck = config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_ENGINE_PPWORDFREQ_CHECK, false);
ppComprWordsCheck = config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_ENGINE_PPCOMPRWORDS_CHECK, false);
// NOTE: These settings should be compared to the global settings done in the LetterEngine constructor.
ppWordsCheck = config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_PP_WORDS_CHECK, false);
if (ppWordsCheck){
ppWordsSettings = new FlatWordsSettings();
ppWordsSettings.maxSize = 150; // Adapt to smaller size.
ppWordsSettings.applyConfig(config, ConfPaths.CHAT_GLOBALCHAT_PP_WORDS);
}
else ppWordsSettings = null; // spare some memory.
ppPrefixesCheck = config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_PP_PREFIXES_CHECK, false);
if (ppPrefixesCheck){
ppPrefixesSettings = new WordPrefixesSettings();
ppPrefixesSettings.maxAdd = 320; // Adapt to smaller size.
ppPrefixesSettings.applyConfig(config, ConfPaths.CHAT_GLOBALCHAT_PP_PREFIXES);
}
else ppPrefixesSettings = null;
ppSimilarityCheck = config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_PP_SIMILARITY_CHECK, false);
if (ppSimilarityCheck){
ppSimilaritySettings = new SimilarWordsBKLSettings();
ppSimilaritySettings.maxSize = 100; // Adapt to smaller size;
ppSimilaritySettings.applyConfig(config, ConfPaths.CHAT_GLOBALCHAT_PP_SIMILARITY);
}
else ppSimilaritySettings = null;
}
}

View File

@ -4,6 +4,10 @@ import java.util.ArrayList;
import java.util.List;
import fr.neatmonster.nocheatplus.checks.chat.ChatConfig;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.SimilarWordsBKL;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.WordPrefixes;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.FlatWords;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.WordProcessor;
/**
* Engine specific player data.
@ -16,15 +20,12 @@ public class EnginePlayerData {
public EnginePlayerData(ChatConfig cc) {
EnginePlayerConfig config = cc.globalChatEnginePlayerConfig;
if (config.ppWordFrequencyCheck){
// TODO: configure.
processors.add(new FlatWordBuckets(50, 4, 1500, 0.9f));
}
if (config.ppComprWordsCheck){
// TODO: configure.
processors.add(new CompressedWords(30000, 320, false));
}
if (config.ppWordsCheck)
processors.add(new FlatWords("ppWords", config.ppWordsSettings));
if (config.ppPrefixesCheck)
processors.add(new WordPrefixes("ppPrefixes", config.ppPrefixesSettings));
if (config.ppSimilarityCheck)
processors.add(new SimilarWordsBKL("ppSimilarity", config.ppSimilaritySettings));
}
}

View File

@ -2,6 +2,7 @@ package fr.neatmonster.nocheatplus.checks.chat.analysis.engine;
import fr.neatmonster.nocheatplus.checks.chat.ChatConfig;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.ManagedMap;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.WordProcessor;
/**
* Store EnginePlayerData. Expire data on get(String, Chatonfig).
@ -32,7 +33,7 @@ public class EnginePlayerDataMap extends ManagedMap<String, EnginePlayerData> {
put(key, data);
}
final long ts = System.currentTimeMillis();
if (ts - durExpire > lastAccess) expire(ts - durExpire);
if (ts - lastAccess > durExpire) expire(ts - durExpire);
lastAccess = ts;
return data;
}

View File

@ -1,55 +0,0 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine;
import java.util.LinkedHashMap;
import fr.neatmonster.nocheatplus.checks.chat.analysis.MessageLetterCount;
import fr.neatmonster.nocheatplus.checks.chat.analysis.WordLetterCount;
import fr.neatmonster.nocheatplus.utilities.ActionFrequency;
/**
* Words mapped to ActionFrequency queues.
* @author mc_dev
*
*/
public class FlatWordBuckets extends AbstractWordProcessor{
final int maxSize;
final LinkedHashMap<String, ActionFrequency> entries;
final long durBucket;
final int nBuckets;
final float factor;
public FlatWordBuckets(int maxSize, int nBuckets, long durBucket, float factor){
super("FlatWordBuckets");
this.maxSize = maxSize;
entries = new LinkedHashMap<String, ActionFrequency>(maxSize);
this.nBuckets = nBuckets;
this.durBucket = durBucket;
this.factor = factor;
}
@Override
public void start(final MessageLetterCount message) {
if (entries.size() + message.words.length > maxSize)
releaseMap(entries, Math.max(message.words.length, maxSize / 10));
}
@Override
public float loop(long ts, int index, String key, WordLetterCount word) {
ActionFrequency freq = entries.get(key);
if (freq == null){
freq = new ActionFrequency(nBuckets, durBucket);
entries.put(key, freq);
return 0.0f;
}
freq.update(ts);
float score = Math.min(1.0f, freq.getScore(factor));
freq.add(ts, 1.0f);
return score;
}
@Override
public void clear() {
entries.clear();
}
}

View File

@ -1,11 +1,22 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bukkit.Bukkit;
import fr.neatmonster.nocheatplus.checks.chat.ChatConfig;
import fr.neatmonster.nocheatplus.checks.chat.ChatData;
import fr.neatmonster.nocheatplus.checks.chat.analysis.MessageLetterCount;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.FlatWords;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.FlatWords.FlatWordsSettings;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.SimilarWordsBKL;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.SimilarWordsBKL.SimilarWordsBKLSettings;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.WordPrefixes;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.WordPrefixes.WordPrefixesSettings;
import fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors.WordProcessor;
import fr.neatmonster.nocheatplus.config.ConfPaths;
import fr.neatmonster.nocheatplus.config.ConfigFile;
@ -17,50 +28,65 @@ import fr.neatmonster.nocheatplus.config.ConfigFile;
*/
public class LetterEngine {
/** Global processors */
protected final List<WordProcessor> processors = new ArrayList<WordProcessor>();
protected final EnginePlayerDataMap dataMap;
public LetterEngine(ConfigFile config){
// Add word processors.
if (config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_ENGINE_GLWORDFREQ_CHECK, false)){
// TODO: Make aspects configurable.
processors.add(new FlatWordBuckets(1000, 4, 1500, 0.9f));
// NOTE: These settings should be compared to the per player settings done in the EnginePlayerConfig constructor.
if (config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_GL_WORDS_CHECK, false)){
FlatWordsSettings settings = new FlatWordsSettings();
settings.maxSize = 1000;
settings.applyConfig(config, ConfPaths.CHAT_GLOBALCHAT_GL_WORDS);
processors.add(new FlatWords("glWords",settings));
}
if (config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_ENGINE_GLCOMPRWORDS_CHECK, false)){
// TODO: Make aspects configurable.
processors.add(new CompressedWords(30000, 2000, false));
if (config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_GL_PREFIXES_CHECK , false)){
WordPrefixesSettings settings = new WordPrefixesSettings();
settings.maxAdd = 2000;
settings.applyConfig(config, ConfPaths.CHAT_GLOBALCHAT_GL_PREFIXES);
processors.add(new WordPrefixes("glPrefixes", settings));
}
// TODO: At least expiration duration configurable?
if (config.getBoolean(ConfPaths.CHAT_GLOBALCHAT_GL_SIMILARITY_CHECK , false)){
SimilarWordsBKLSettings settings = new SimilarWordsBKLSettings();
settings.maxSize = 1000;
settings.applyConfig(config, ConfPaths.CHAT_GLOBALCHAT_GL_SIMILARITY);
processors.add(new SimilarWordsBKL("glSimilarity", settings));
}
// TODO: At least expiration duration configurable? (Entries expire after 10 minutes.)
dataMap = new EnginePlayerDataMap(600000L, 100, 0.75f);
}
public float process(final MessageLetterCount letterCount, final String playerName, final ChatConfig cc, final ChatData data){
float score = 0;
public Map<String, Float> process(final MessageLetterCount letterCount, final String playerName, final ChatConfig cc, final ChatData data){
final Map<String, Float> result = new HashMap<String, Float>();
// Global processors.
for (final WordProcessor processor : processors){
final float refScore = processor.process(letterCount);
// System.out.println("global:" + processor.getProcessorName() +": " + refScore);
score = Math.max(score, refScore);
try{
result.put(processor.getProcessorName(), processor.process(letterCount) * cc.globalChatGlobalWeight);
}
catch( final Exception e){
Bukkit.getLogger().warning("[NoCheatPlus] globalchat: processor("+processor.getProcessorName()+") generated an exception: " + e.getClass().getSimpleName() + ": " + e.getMessage());
e.printStackTrace();
continue;
}
}
// Per player processors.
final EnginePlayerData engineData = dataMap.get(playerName, cc);
for (final WordProcessor processor : engineData.processors){
final float refScore = processor.process(letterCount);
// System.out.println("player: " + processor.getProcessorName() +": " + refScore);
score = Math.max(score, refScore);
try{
result.put(processor.getProcessorName(), processor.process(letterCount) * cc.globalChatPlayerWeight);
}
catch( final Exception e){
Bukkit.getLogger().warning("[NoCheatPlus] globalchat: processor("+processor.getProcessorName()+") generated an exception: " + e.getClass().getSimpleName() + ": " + e.getMessage());
e.printStackTrace();
continue;
}
}
// TODO: Is max the right method?
return score;
return result;
}
public void clear() {

View File

@ -1,4 +1,4 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine;
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors;
import java.util.LinkedList;
import java.util.List;
@ -28,6 +28,9 @@ public abstract class AbstractWordProcessor implements WordProcessor{
}
protected String name;
/** Not set by constructor. */
protected float weight = 1f;
public AbstractWordProcessor(String name){
this.name = name;
}
@ -37,6 +40,17 @@ public abstract class AbstractWordProcessor implements WordProcessor{
return name;
}
@Override
public float getWeight() {
return weight;
}
public void setWeight(float weight){
this.weight = weight;
}
@Override
public float process(final MessageLetterCount message) {
// Does the looping, scores are summed up and divided by number of words.
@ -46,9 +60,9 @@ public abstract class AbstractWordProcessor implements WordProcessor{
for (int index = 0; index < message.words.length; index++){
final WordLetterCount word = message.words[index];
final String key = word.word.toLowerCase();
score += loop(ts, index, key, word);
score += loop(ts, index, key, word) * (float) (word.word.length() + 1);
}
score /= (float) message.words.length;
score /= (float) (message.message.length() + message.words.length);
return score;
}
@ -65,7 +79,7 @@ public abstract class AbstractWordProcessor implements WordProcessor{
* Process one word.
* @param index
* @param message
* @return Score.
* @return Score, suggested to be within [0 .. 1].
*/
public abstract float loop(final long ts, final int index, final String key, final WordLetterCount word);
}

View File

@ -0,0 +1,149 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import fr.neatmonster.nocheatplus.checks.chat.analysis.WordLetterCount;
import fr.neatmonster.nocheatplus.config.ConfigFile;
public abstract class DigestedWords extends AbstractWordProcessor{
/**
* Doubling code a little for the sake of flexibility with config reading.
* @author mc_dev
*
*/
public static class DigestedWordsSettings{
public boolean sort = false;
public boolean compress = false;
public boolean split = false;
public float weight = 1f;
public DigestedWordsSettings(){
}
public DigestedWordsSettings(boolean sort, boolean compress, boolean split, float weight) {
this.sort = sort;
this.compress = compress;
this.split = split;
this.weight = weight;
}
/**
* Returns this object.
* @param config
* @param prefix Prefix for direct addition of config path.
* @return This object.
*/
public DigestedWordsSettings applyConfig(ConfigFile config, String prefix){
this.sort = config.getBoolean(prefix + "sort", this.sort);
this.compress = config.getBoolean(prefix + "compress", this.compress);
this.split = config.getBoolean(prefix + "split", this.split);
this.weight = (float) config.getDouble(prefix + "weight", this.weight);
return this;
}
}
protected final boolean sort;
protected final boolean compress;
protected final boolean split;
protected final List<Character> letters = new ArrayList<Character>(10);
protected final List<Character> digits = new ArrayList<Character>(10);
protected final List<Character> other = new ArrayList<Character>(10);
/**
* Constructor for a given settings instance.
* @param name
* @param settings
*/
public DigestedWords(String name, DigestedWordsSettings settings){
this(name, settings.sort, settings.compress, settings.split);
this.weight = settings.weight;
}
/**
*
* @param durExpire
* @param maxAdd
* @param sort Sort letters.
* @param compress Only use every letter once.
* @param split Check for letters, digits, other individually (!).
*/
public DigestedWords(String name, boolean sort, boolean compress, boolean split) {
super(name);
this.sort = sort;
this.compress = compress;
this.split = split;
}
@Override
public float loop(final long ts, final int index, final String key, final WordLetterCount word) {
letters.clear();
digits.clear();
other.clear();
Collection<Character> chars;
if (compress) chars = word.counts.keySet();
else{
// Add all.
chars = new ArrayList<Character>(word.word.length());
for (int i = 0; i < word.word.length(); i++){
char c = word.word.charAt(i);
if (Character.isUpperCase(c)) c = Character.toLowerCase(c);
chars.add(c);
// hmm. Maybe provide all the lists in the WordLetterCount already.
}
}
final int len = chars.size();
for (Character c : chars){
if (!split || Character.isLetter(c)) letters.add(c);
else if (Character.isDigit(c)) digits.add(c);
else other.add(c);
}
if (sort){
Collections.sort(letters);
Collections.sort(digits);
Collections.sort(other);
}
float score = 0;
if (!letters.isEmpty()){
score += getScore(letters, ts) * (float) letters.size();
}
if (!digits.isEmpty()){
score += getScore(digits, ts) * (float) digits.size();
}
if (!other.isEmpty()){
score += getScore(other, ts) * (float) other.size();
}
return len == 0?0f:(score / (float) len);
}
@Override
public void clear() {
letters.clear();
digits.clear();
other.clear();
}
public static final char[] toArray(final Collection<Character> chars){
final char[] a = new char[chars.size()];
int i = 0;
for (final Character c : chars){
a[i] = c;
i ++;
// TODO: lol, horrible.
}
return a;
}
/**
*
* @param chars List of characters, should be lower case, could be split / compressed.
* @param ts common timestamp for processing the whole message.
* @return
*/
protected abstract float getScore(final List<Character> chars, final long ts);
}

View File

@ -0,0 +1,99 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors;
import java.util.LinkedHashMap;
import java.util.List;
import fr.neatmonster.nocheatplus.checks.chat.analysis.MessageLetterCount;
import fr.neatmonster.nocheatplus.config.ConfigFile;
import fr.neatmonster.nocheatplus.utilities.ActionFrequency;
/**
* Words mapped to ActionFrequency queues.
* @author mc_dev
*
*/
public class FlatWords extends DigestedWords{
public static class FlatWordsSettings extends DigestedWordsSettings{
public int maxSize = 1000;
public long durBucket = 1500;
public int nBuckets = 4;
public float factor = 0.9f;
/**
* split by default.
*/
public FlatWordsSettings(){
super(false, true, true, 1f);
}
public FlatWordsSettings (int maxSize, int nBuckets, long durBucket, float factor, boolean sort, boolean compress, boolean split, float weight){
super(sort, compress, split, weight);
this.maxSize = maxSize;
this.nBuckets = nBuckets;
this.durBucket = durBucket;
this.factor = factor;
}
public FlatWordsSettings applyConfig(ConfigFile config, String prefix){
super.applyConfig(config, prefix);
this.maxSize = config.getInt(prefix + "size", this.maxSize);
this.nBuckets = config.getInt(prefix + "buckets", this.nBuckets);
// In seconds.
this.durBucket = (long) (config.getDouble(prefix + "time", (float) this.durBucket / 1000f) * 1000f);
this.factor = (float) config.getDouble(prefix + "factor", this.factor);
return this;
}
}
protected final int maxSize;
protected final LinkedHashMap<String, ActionFrequency> entries;
protected final long durBucket;
protected final int nBuckets;
protected final float factor;
protected long lastAdd = System.currentTimeMillis();
public FlatWords(String name, FlatWordsSettings settings){
this(name, settings.maxSize, settings.nBuckets, settings.durBucket, settings.factor, settings.sort, settings.compress, settings.split);
this.weight = settings.weight;
}
public FlatWords(String name, int maxSize, int nBuckets, long durBucket, float factor, boolean sort, boolean compress, boolean split){
super(name, sort, compress, split);
this.maxSize = maxSize;
entries = new LinkedHashMap<String, ActionFrequency>(maxSize);
this.nBuckets = nBuckets;
this.durBucket = durBucket;
this.factor = factor;
}
@Override
public void start(final MessageLetterCount message) {
if (System.currentTimeMillis() - lastAdd > nBuckets * durBucket)
entries.clear();
else if (entries.size() + message.words.length > maxSize)
releaseMap(entries, Math.max(message.words.length, maxSize / 10));
}
@Override
public void clear() {
super.clear();
entries.clear();
}
@Override
protected float getScore(List<Character> chars, long ts) {
lastAdd = ts;
final char[] a = DigestedWords.toArray(chars);
final String key = new String(a);
ActionFrequency freq = entries.get(key);
if (freq == null){
freq = new ActionFrequency(nBuckets, durBucket);
entries.put(key, freq);
return 0.0f;
}
freq.update(ts);
float score = Math.min(1.0f, freq.getScore(factor));
freq.add(ts, 1.0f);
return score;
}
}

View File

@ -0,0 +1,102 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors;
import java.util.List;
import fr.neatmonster.nocheatplus.checks.chat.analysis.MessageLetterCount;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.SimpleTimedBKLevenshtein;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.SimpleTimedBKLevenshtein.STBKLResult;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.bktree.TimedBKLevenshtein.SimpleTimedLevenNode;
import fr.neatmonster.nocheatplus.config.ConfigFile;
public class SimilarWordsBKL extends DigestedWords {
public static class SimilarWordsBKLSettings extends DigestedWordsSettings{
public int maxSize = 1000;
public int range = 2;
public long durExpire = 30000;
public int maxSeek = 0;
/**
* split + compress by default.
*/
public SimilarWordsBKLSettings(){
super(false, true, true, 1f);
}
public SimilarWordsBKLSettings(int range, long durExpire, int maxSize, int maxSeek, boolean sort, boolean compress, boolean split, float weight) {
super(sort, compress, split, weight);
this.range = range;
this.durExpire = durExpire;
this.maxSize = maxSize;
this.maxSeek = maxSeek;
}
public SimilarWordsBKLSettings applyConfig(ConfigFile config, String prefix){
super.applyConfig(config, prefix);
this.maxSize = config.getInt(prefix + "size", this.maxSize);
this.maxSeek= config.getInt(prefix + "seek", this.maxSeek);
this.durExpire = (long) (config.getDouble(prefix + "time", (float) this.durExpire / 1000f) * 1000f);
return this;
}
}
protected final SimpleTimedBKLevenshtein tree = new SimpleTimedBKLevenshtein();
protected int added = 0;
protected final int maxSize;
protected final int range;
protected final long durExpire;
protected final int maxSeek;
protected long lastAdd = System.currentTimeMillis();
public SimilarWordsBKL(String name, SimilarWordsBKLSettings settings){
this(name, settings.range, settings.durExpire, settings.maxSize, settings.maxSeek , settings.sort, settings.compress, settings.split);
this.weight = settings.weight;
}
public SimilarWordsBKL(String name, int range, long durExpire, int maxSize, int maxSeek, boolean sort, boolean compress, boolean split) {
super(name, sort, compress, split);
this.maxSize = maxSize;
this.range = range;
this.durExpire = durExpire;
this.maxSeek = maxSeek;
}
@Override
public void clear() {
super.clear();
tree.clear();
}
@Override
public void start(final MessageLetterCount message) {
if (added + message.words.length > maxSize || System.currentTimeMillis() - lastAdd > durExpire) tree.clear();
}
@Override
protected float getScore(final List<Character> chars, final long ts) {
// TODO: very short words, very long words.
lastAdd = ts;
final char[] a = DigestedWords.toArray(chars);
final STBKLResult result = tree.lookup(a, range, maxSeek, true);
if (result.isNew) added ++;
// Calculate time score.
float score = 0f;
if (!result.isNew && result.match != null){
final long age = ts - result.match.ts;
result.match.ts = ts;
if (age < durExpire)
score = Math.max(score, (float) (durExpire - age) / (float) durExpire);
}
for (final SimpleTimedLevenNode node : result.nodes){
final long age = ts - node.ts;
node.ts = ts;
if (age < durExpire)
score = Math.max(score, (float) (durExpire - age) / (float) durExpire);
}
return score;
}
}

View File

@ -0,0 +1,99 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors;
import java.util.List;
import fr.neatmonster.nocheatplus.checks.chat.analysis.MessageLetterCount;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.prefixtree.SimpleTimedCharPrefixTree;
import fr.neatmonster.nocheatplus.checks.chat.analysis.ds.prefixtree.SimpleTimedCharPrefixTree.SimpleTimedCharLookupEntry;
import fr.neatmonster.nocheatplus.config.ConfigFile;
public class WordPrefixes extends DigestedWords{
public static class WordPrefixesSettings extends DigestedWordsSettings{
public int maxAdd = 1000;
public long durExpire = 30000;
/**
* split and compress by default.
*/
public WordPrefixesSettings(){
super(false, true, true, 1f);
}
public WordPrefixesSettings(long durExpire, int maxAdd, boolean sort, boolean compress, boolean split, float weight) {
super(sort, compress, split, weight);
this.maxAdd = maxAdd;
this.durExpire = durExpire;
}
public WordPrefixesSettings applyConfig(ConfigFile config, String prefix){
super.applyConfig(config, prefix);
this.maxAdd = config.getInt(prefix + "size", this.maxAdd);
this.durExpire = (long) (config.getDouble(prefix + "time", (float) this.durExpire / 1000f) * 1000f);
return this;
}
}
protected final SimpleTimedCharPrefixTree tree = new SimpleTimedCharPrefixTree(true);
protected final int maxAdd;
protected int added = 0;
protected final long durExpire;
protected long lastAdd = System.currentTimeMillis();
public WordPrefixes(String name, WordPrefixesSettings settings){
this(name, settings.durExpire, settings.maxAdd, settings.sort, settings.compress, settings.split);
this.weight = settings.weight;
}
/**
*
* @param durExpire
* @param maxAdd
* @param sort Sort letters.
* @param compress Only use every letter once.
* @param split Check for letters, digits, other individually (!).
*/
public WordPrefixes(String name, long durExpire, int maxAdd, boolean sort, boolean compress, boolean split) {
super(name, sort, compress, split);
this.durExpire = durExpire;
this.maxAdd = maxAdd;
}
@Override
public void start(final MessageLetterCount message) {
// This allows adding up to maximum messge length more characters,
// but also allows to set size of nodes exactly.
// TODO: Some better method than blunt clear (extra LinkedHashSet/LRU?).
if (added > maxAdd || System.currentTimeMillis() - lastAdd > durExpire){
tree.clear();
added = 0;
}
}
@Override
public void clear() {
super.clear();
tree.clear();
}
protected float getScore(final List<Character> chars, final long ts) {
lastAdd = ts;
final int len = chars.size();
final SimpleTimedCharLookupEntry entry = tree.lookup(chars, true);
final int depth = entry.depth;
float score = 0f;
for (int i = 0; i < depth ; i++){
final long age = ts - entry.timeInsertion[i];
if (age < durExpire)
score += 1f / (float) (depth - i) * (float) (durExpire - age) / (float) durExpire;
}
if (depth == len){
score += 0.2;
if (entry.insertion.isEnd) score += 0.2;
}
if (len != depth) added += len - depth;
return score;
}
}

View File

@ -1,4 +1,4 @@
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine;
package fr.neatmonster.nocheatplus.checks.chat.analysis.engine.processors;
import fr.neatmonster.nocheatplus.checks.chat.analysis.MessageLetterCount;
@ -11,6 +11,12 @@ public interface WordProcessor{
*/
public String getProcessorName();
/**
* Configured weight.
* @return
*/
public float getWeight();
/**
*
* @param message

View File

@ -141,25 +141,39 @@ public abstract class ConfPaths {
public static final String CHAT_COLOR_CHECK = CHAT_COLOR + "active";
public static final String CHAT_COLOR_ACTIONS = CHAT_COLOR + "actions";
// globalchat
private static final String CHAT_GLOBALCHAT = CHAT + "globalchat.";
public static final String CHAT_GLOBALCHAT_CHECK = CHAT_GLOBALCHAT + "active";
private static final String CHAT_GLOBALCHAT_ENGINE = CHAT_GLOBALCHAT + "engine.";
public static final String CHAT_GLOBALCHAT_ENGINE_CHECK = CHAT_GLOBALCHAT_ENGINE + "active";
private static final String CHAT_GLOBALCHAT_ENGINE_GLWORDFREQ = CHAT_GLOBALCHAT_ENGINE + "glwordfrequency.";
public static final String CHAT_GLOBALCHAT_ENGINE_GLWORDFREQ_CHECK = CHAT_GLOBALCHAT_ENGINE_GLWORDFREQ + "active";
public static final String CHAT_GLOBALCHAT_ENGINE_GLCOMPRWORDS = CHAT_GLOBALCHAT_ENGINE + "glcompressedwords.";
public static final String CHAT_GLOBALCHAT_ENGINE_GLCOMPRWORDS_CHECK = CHAT_GLOBALCHAT_ENGINE_GLCOMPRWORDS + "active";
private static final String CHAT_GLOBALCHAT_ENGINE_PPCOMPRWORDS = CHAT_GLOBALCHAT_ENGINE + "ppcompressedwords.";
public static final String CHAT_GLOBALCHAT_ENGINE_PPCOMPRWORDS_CHECK = CHAT_GLOBALCHAT_ENGINE_PPCOMPRWORDS + "check";
private static final String CHAT_GLOBALCHAT_ENGINE_PPWORDFREQ = CHAT_GLOBALCHAT_ENGINE + "ppwordfrequency.";
public static final String CHAT_GLOBALCHAT_ENGINE_PPWORDFREQ_CHECK = CHAT_GLOBALCHAT_ENGINE_PPWORDFREQ + "active";
public static final String CHAT_GLOBALCHAT_COMMANDS = CHAT_GLOBALCHAT + "commands";
public static final String CHAT_GLOBALCHAT_LEVEL = CHAT_GLOBALCHAT + "level";
public static final String CHAT_GLOBALCHAT_ENGINE_MAXIMUM = CHAT_GLOBALCHAT + "maximum";
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_COMMANDS = CHAT_GLOBALCHAT + "commands";
// (Some of the following paths must be public for generic config reading.)
// Extended global checks.
private static final String CHAT_GLOBALCHAT_GL = CHAT_GLOBALCHAT + "global.";
public static final String CHAT_GLOBALCHAT_GL_WEIGHT = CHAT_GLOBALCHAT_GL + "weight";
public static final String CHAT_GLOBALCHAT_GL_WORDS = CHAT_GLOBALCHAT_GL + "words.";
public static final String CHAT_GLOBALCHAT_GL_WORDS_CHECK = CHAT_GLOBALCHAT_GL_WORDS + "active";
public static final String CHAT_GLOBALCHAT_GL_PREFIXES = CHAT_GLOBALCHAT_GL + "prefixes.";
public static final String CHAT_GLOBALCHAT_GL_PREFIXES_CHECK = CHAT_GLOBALCHAT_GL_PREFIXES + "active";
public static final String CHAT_GLOBALCHAT_GL_SIMILARITY = CHAT_GLOBALCHAT_GL + "similarity.";
public static final String CHAT_GLOBALCHAT_GL_SIMILARITY_CHECK = CHAT_GLOBALCHAT_GL_SIMILARITY + "active";
// Extended per player checks.
private static final String CHAT_GLOBALCHAT_PP = CHAT_GLOBALCHAT + "player.";
public static final String CHAT_GLOBALCHAT_PP_WEIGHT = CHAT_GLOBALCHAT_PP + "weight";
public static final String CHAT_GLOBALCHAT_PP_PREFIXES = CHAT_GLOBALCHAT_PP + "prefixes.";
public static final String CHAT_GLOBALCHAT_PP_PREFIXES_CHECK = CHAT_GLOBALCHAT_PP_PREFIXES + "active";
public static final String CHAT_GLOBALCHAT_PP_WORDS = CHAT_GLOBALCHAT_PP + "words.";
public static final String CHAT_GLOBALCHAT_PP_WORDS_CHECK = CHAT_GLOBALCHAT_PP_WORDS + "active";
public static final String CHAT_GLOBALCHAT_PP_SIMILARITY = CHAT_GLOBALCHAT_PP + "similarity.";
public static final String CHAT_GLOBALCHAT_PP_SIMILARITY_CHECK = CHAT_GLOBALCHAT_PP_SIMILARITY + "active";
// globalchat actions
public static final String CHAT_GLOBALCHAT_DEBUG = CHAT_GLOBALCHAT + "debug";
public static final String CHAT_GLOBALCHAT_ACTIONS = CHAT_GLOBALCHAT + "actions";
// nopwnage
private static final String CHAT_NOPWNAGE = CHAT + "nopwnage.";
public static final String CHAT_NOPWNAGE_CHECK = CHAT_NOPWNAGE + "active";
public static final String CHAT_NOPWNAGE_EXCLUSIONS = CHAT_NOPWNAGE + "exclusions";

View File

@ -128,21 +128,24 @@ public class DefaultConfig extends ConfigFile {
set(ConfPaths.CHAT_COLOR_CHECK, true);
set(ConfPaths.CHAT_COLOR_ACTIONS, "log:color:0:1:if cancel");
// globalchat (ordering on purpose).
set(ConfPaths.CHAT_GLOBALCHAT_CHECK, true);
set(ConfPaths.CHAT_GLOBALCHAT_COMMANDS, new LinkedList<String>(Arrays.asList(
new String[]{"/me"})));
set(ConfPaths.CHAT_GLOBALCHAT_ENGINE_CHECK, false);
// Individual engine settings: maybe hide later by checking another "expert" or "show-hidden" flag.
set(ConfPaths.CHAT_GLOBALCHAT_ENGINE_GLWORDFREQ_CHECK, false);
set(ConfPaths.CHAT_GLOBALCHAT_ENGINE_GLCOMPRWORDS_CHECK, false);
set(ConfPaths.CHAT_GLOBALCHAT_ENGINE_PPWORDFREQ_CHECK, false);
set(ConfPaths.CHAT_GLOBALCHAT_ENGINE_PPCOMPRWORDS_CHECK, true);
//
set(ConfPaths.CHAT_GLOBALCHAT_LEVEL, 80);
set(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_FACTOR, 0.9D);
set(ConfPaths.CHAT_GLOBALCHAT_FREQUENCY_WEIGHT, 6);
set(ConfPaths.CHAT_GLOBALCHAT_LEVEL, 80);
set(ConfPaths.CHAT_GLOBALCHAT_COMMANDS,
new LinkedList<String>(Arrays.asList(new String[]{"/me"})));
set(ConfPaths.CHAT_GLOBALCHAT_GL_WEIGHT, 0.5);
set(ConfPaths.CHAT_GLOBALCHAT_GL_WORDS_CHECK, false);
set(ConfPaths.CHAT_GLOBALCHAT_GL_PREFIXES_CHECK , false);
set(ConfPaths.CHAT_GLOBALCHAT_GL_SIMILARITY_CHECK , false);
set(ConfPaths.CHAT_GLOBALCHAT_PP_WORDS_CHECK, false);
set(ConfPaths.CHAT_GLOBALCHAT_PP_PREFIXES_CHECK, false);
set(ConfPaths.CHAT_GLOBALCHAT_PP_SIMILARITY_CHECK , false);
//
set(ConfPaths.CHAT_GLOBALCHAT_ACTIONS, "log:globalchat:0:5:f cancel cmd:tellglchat vl>20 log:globalchat:0:5:if cancel cmd:kickglchat");
// nopwnage
set(ConfPaths.CHAT_NOPWNAGE_CHECK, true);
set(ConfPaths.CHAT_NOPWNAGE_EXCLUSIONS, new ArrayList<String>());
set(ConfPaths.CHAT_NOPWNAGE_LEVEL, 500);
@ -153,7 +156,7 @@ public class DefaultConfig extends ConfigFile {
set(ConfPaths.CHAT_NOPWNAGE_CAPTCHA_CHECK, true);
set(ConfPaths.CHAT_NOPWNAGE_CAPTCHA_CHARACTERS,
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
"abcdefghjkmnpqrtuvwxyzABCDEFGHJKMNPQRTUVWXYZ2346789");
set(ConfPaths.CHAT_NOPWNAGE_CAPTCHA_LENGTH, 6);
set(ConfPaths.CHAT_NOPWNAGE_CAPTCHA_QUESTION,
"&cPlease type '&6[captcha]&c' to continue sending messages/commands.");

View File

@ -1,7 +1,12 @@
package fr.neatmonster.nocheatplus.utilities;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
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;
import org.bukkit.entity.Player;
@ -14,6 +19,17 @@ public class CheckUtils {
/** The file logger. */
public static Logger fileLogger = null;
/** Decimal format for "#.###" */
public static final DecimalFormat fdec3 = new DecimalFormat();
static{
DecimalFormatSymbols sym = fdec3.getDecimalFormatSymbols();
sym.setDecimalSeparator('.');
fdec3.setDecimalFormatSymbols(sym);
fdec3.setMaximumFractionDigits(3);
fdec3.setMinimumIntegerDigits(1);
}
/**
* Check if a player looks at a target of a specific size, with a specific precision value (roughly).
@ -158,6 +174,53 @@ public class CheckUtils {
return p[n];
}
/**
* Join parts with link.
* @param input
* @param link
* @return
*/
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){
if (!first) builder.append(link);
builder.append(obj.toString());
first = false;
}
return builder.toString();
}
/**
* Convenience method.
* @param parts
* @param link
* @return
*/
public static <O extends Object> boolean scheduleOutputJoined(final List<O> parts, String link){
return scheduleOutput(join(parts, link));
}
/**
* Schedule a message to be output by the bukkit logger.
* @param message
* @return If scheduled successfully.
*/
public static boolean scheduleOutput(final String message){
try{
return Bukkit.getScheduler().scheduleSyncDelayedTask(Bukkit.getPluginManager().getPlugin("NoCheatPlus"),
new Runnable() {
@Override
public void run() {
Bukkit.getLogger().info(message);
}
}) != -1;
}
catch (final Exception exc){
return false;
}
}
/**
* Removes the colors of a message.