mirror of
https://github.com/NoCheatPlus/NoCheatPlus.git
synced 2025-01-06 07:47:35 +01:00
Re-structure globalchat: rename config paths, add hidden settings, add
similarity check. Adjust default captcha letters (Ticket 194).
This commit is contained in:
parent
89a5b3221f
commit
d4103899a5
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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";
|
||||
|
@ -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.");
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user