mirror of
https://github.com/NoCheatPlus/NoCheatPlus.git
synced 2024-11-02 08:40:01 +01:00
Bleeding: Add global word to ActionFreqeuncy map, factor to analysis
package.
This commit is contained in:
parent
1936a2f158
commit
386a70eda4
@ -26,7 +26,7 @@ public class ActionList {
|
|||||||
/** This is a very bad design decision, but it's also really convenient to define this here. */
|
/** This is a very bad design decision, but it's also really convenient to define this here. */
|
||||||
public final String permissionSilent;
|
public final String permissionSilent;
|
||||||
|
|
||||||
/** If there are no actions registered, we still return an Array. It's just empty/size=0. */
|
/** If there are no actions registered, we still return an Array. It's just empty/maxSize=0. */
|
||||||
private final static Action[] emptyArray = new Action[0];
|
private final static Action[] emptyArray = new Action[0];
|
||||||
|
|
||||||
/** The actions of this ActionList, "bundled" by treshold (violation level). */
|
/** The actions of this ActionList, "bundled" by treshold (violation level). */
|
||||||
|
@ -4,7 +4,9 @@ import org.bukkit.entity.Player;
|
|||||||
|
|
||||||
import fr.neatmonster.nocheatplus.checks.Check;
|
import fr.neatmonster.nocheatplus.checks.Check;
|
||||||
import fr.neatmonster.nocheatplus.checks.CheckType;
|
import fr.neatmonster.nocheatplus.checks.CheckType;
|
||||||
import fr.neatmonster.nocheatplus.checks.chat.MessageLetterCount.WordLetterCount;
|
import fr.neatmonster.nocheatplus.checks.chat.analysis.LetterEngine;
|
||||||
|
import fr.neatmonster.nocheatplus.checks.chat.analysis.MessageLetterCount;
|
||||||
|
import fr.neatmonster.nocheatplus.checks.chat.analysis.WordLetterCount;
|
||||||
import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;
|
import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,6 +16,8 @@ import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;
|
|||||||
*/
|
*/
|
||||||
public class GlobalChat extends Check{
|
public class GlobalChat extends Check{
|
||||||
|
|
||||||
|
private final LetterEngine engine = new LetterEngine();
|
||||||
|
|
||||||
public GlobalChat() {
|
public GlobalChat() {
|
||||||
super(CheckType.CHAT_GLOBALCHAT);
|
super(CheckType.CHAT_GLOBALCHAT);
|
||||||
}
|
}
|
||||||
@ -79,6 +83,8 @@ public class GlobalChat extends Check{
|
|||||||
|
|
||||||
// (Following: random/made up criteria.)
|
// (Following: random/made up criteria.)
|
||||||
|
|
||||||
|
// TODO: Create tests for all methods with wordlists, fake chat (refactor for that).
|
||||||
|
|
||||||
// Full message processing. ------------
|
// Full message processing. ------------
|
||||||
|
|
||||||
// Upper case.
|
// Upper case.
|
||||||
@ -128,7 +134,9 @@ public class GlobalChat extends Check{
|
|||||||
wWords /= (float) letterCounts.words.length;
|
wWords /= (float) letterCounts.words.length;
|
||||||
score += wWords;
|
score += wWords;
|
||||||
|
|
||||||
// TODO: LetterEngine ? + core checks
|
// Engine:
|
||||||
|
final float wEngine = engine.feed(letterCounts);
|
||||||
|
score += wEngine;
|
||||||
|
|
||||||
// Wrapping it up. --------------------
|
// Wrapping it up. --------------------
|
||||||
// Add weight to frequency counts.
|
// Add weight to frequency counts.
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
package fr.neatmonster.nocheatplus.checks.chat;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Letter count for a message and sub words, including upper case count.<br>
|
|
||||||
* NOTE: this is a pretty heavy implementation for testing purposes.
|
|
||||||
* @author mc_dev
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class MessageLetterCount {
|
|
||||||
/**
|
|
||||||
* Letter count for a word.
|
|
||||||
* @author mc_dev
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public static final class WordLetterCount{
|
|
||||||
public final String word;
|
|
||||||
public final Map<Character, Integer> counts;
|
|
||||||
public final int upperCase;
|
|
||||||
public final int notLetter;
|
|
||||||
public WordLetterCount(final String word){
|
|
||||||
this.word = word;
|
|
||||||
char[] a = word.toCharArray();
|
|
||||||
// Preserve insertion order.
|
|
||||||
counts = new LinkedHashMap<Character, Integer>(a.length);
|
|
||||||
int upperCase = 0;
|
|
||||||
int notLetter = 0;
|
|
||||||
for (int i = 0; i < a.length; i++){
|
|
||||||
final char c = a[i];
|
|
||||||
final Character key;
|
|
||||||
if (!Character.isLetter(c)) notLetter ++;
|
|
||||||
if (Character.isUpperCase(c)){
|
|
||||||
upperCase ++;
|
|
||||||
key = Character.toLowerCase(c);
|
|
||||||
}
|
|
||||||
else key = c;
|
|
||||||
final Integer count = counts.remove(key);
|
|
||||||
if (count == null) counts.put(key, 1);
|
|
||||||
else counts.put(key, count.intValue() + 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
this.notLetter = notLetter;
|
|
||||||
this.upperCase = upperCase;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getNotLetterRatio(){
|
|
||||||
return (float) notLetter / (float) word.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getLetterCountRatio(){
|
|
||||||
return (float) counts.size() / (float) word.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getUpperCaseRatio(){
|
|
||||||
return (float) upperCase / (float) word.length();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String message;
|
|
||||||
|
|
||||||
public final String split;
|
|
||||||
|
|
||||||
public final WordLetterCount[] words;
|
|
||||||
|
|
||||||
public final WordLetterCount fullCount;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for splitting by a space.
|
|
||||||
* @param message
|
|
||||||
*/
|
|
||||||
public MessageLetterCount(final String message){
|
|
||||||
this(message, " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param message
|
|
||||||
* @param split
|
|
||||||
*/
|
|
||||||
public MessageLetterCount(final String message, final String split){
|
|
||||||
this.message = message;
|
|
||||||
this.split = split;
|
|
||||||
|
|
||||||
final String[] parts = message.split(split);
|
|
||||||
words = new WordLetterCount[parts.length];
|
|
||||||
|
|
||||||
fullCount = new WordLetterCount(message);
|
|
||||||
// (Do not store 60 times "a".)
|
|
||||||
final Map<String, WordLetterCount> done = new HashMap<String, MessageLetterCount.WordLetterCount>(words.length);
|
|
||||||
for (int i = 0; i < parts.length; i++){
|
|
||||||
final String word = parts[i];
|
|
||||||
if (done.containsKey(word)){
|
|
||||||
words[i] = done.get(word);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
done.put(word, words[i] = new WordLetterCount(word));
|
|
||||||
}
|
|
||||||
done.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,64 @@
|
|||||||
|
package fr.neatmonster.nocheatplus.checks.chat.analysis;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class AbstractWordProcessor implements WordProcessor{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a number of entries from a map, in order of iteration over a key set.
|
||||||
|
* @param map
|
||||||
|
* @param number Number of entries to remove.
|
||||||
|
*/
|
||||||
|
public static final <K> void releaseMap(final Map<K, ?> map, int number){
|
||||||
|
final List<K> rem = new LinkedList<K>();
|
||||||
|
int i = 0;
|
||||||
|
for (final K key : map.keySet()){
|
||||||
|
rem.add(key);
|
||||||
|
i++;
|
||||||
|
if (i > number) break;
|
||||||
|
}
|
||||||
|
for (final K key : rem){
|
||||||
|
map.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String name;
|
||||||
|
public AbstractWordProcessor(String name){
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProcessorName(){
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float process(final MessageLetterCount message) {
|
||||||
|
// Does the looping, scores are summed up and divided by number of words.
|
||||||
|
start(message);
|
||||||
|
final long ts = System.currentTimeMillis();
|
||||||
|
float score = 0;
|
||||||
|
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 /= (float) message.words.length;
|
||||||
|
// System.out.println(getProcessorName() +": " + score);
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start(final MessageLetterCount message){
|
||||||
|
// Override if needed.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process one word.
|
||||||
|
* @param index
|
||||||
|
* @param message
|
||||||
|
* @return Score.
|
||||||
|
*/
|
||||||
|
public abstract float loop(final long ts, final int index, final String key, final WordLetterCount word);
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package fr.neatmonster.nocheatplus.checks.chat.analysis;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
|
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(MessageLetterCount message) {
|
||||||
|
if (entries.size() + message.words.length > maxSize)
|
||||||
|
releaseMap(entries, maxSize / 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float loop(long ts, int index, String key,
|
||||||
|
WordLetterCount message) {
|
||||||
|
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,31 @@
|
|||||||
|
package fr.neatmonster.nocheatplus.checks.chat.analysis;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process words.
|
||||||
|
* @author mc_dev
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class LetterEngine {
|
||||||
|
|
||||||
|
protected final List<WordProcessor> processors = new ArrayList<WordProcessor>();
|
||||||
|
|
||||||
|
public LetterEngine(){
|
||||||
|
// Add word processors.
|
||||||
|
processors.add(new FlatWordBuckets(1000, 4, 1500, 0.9f));
|
||||||
|
}
|
||||||
|
|
||||||
|
public float feed(final MessageLetterCount letterCount){
|
||||||
|
float score = 0;
|
||||||
|
// Run all processors.
|
||||||
|
for (final WordProcessor processor : processors){
|
||||||
|
final float refScore = processor.process(letterCount);
|
||||||
|
// TODO: max or sum or average or flexible (?)...
|
||||||
|
score = Math.max(score, refScore);
|
||||||
|
}
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package fr.neatmonster.nocheatplus.checks.chat.analysis;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Letter count for a message and sub words, including upper case count.<br>
|
||||||
|
* NOTE: this is a pretty heavy implementation for testing purposes.
|
||||||
|
* @author mc_dev
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class MessageLetterCount {
|
||||||
|
|
||||||
|
public final String message;
|
||||||
|
|
||||||
|
public final String split;
|
||||||
|
|
||||||
|
public final WordLetterCount[] words;
|
||||||
|
|
||||||
|
public final WordLetterCount fullCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for splitting by a space.
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
public MessageLetterCount(final String message){
|
||||||
|
this(message, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @param split
|
||||||
|
*/
|
||||||
|
public MessageLetterCount(final String message, final String split){
|
||||||
|
this.message = message;
|
||||||
|
this.split = split;
|
||||||
|
|
||||||
|
final String[] parts = message.split(split);
|
||||||
|
words = new WordLetterCount[parts.length];
|
||||||
|
|
||||||
|
fullCount = new WordLetterCount(message);
|
||||||
|
// (Do not store 60 times "a".)
|
||||||
|
final Map<String, WordLetterCount> done = new HashMap<String, WordLetterCount>(words.length);
|
||||||
|
for (int i = 0; i < parts.length; i++){
|
||||||
|
final String word = parts[i];
|
||||||
|
if (done.containsKey(word)){
|
||||||
|
words[i] = done.get(word);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
done.put(word, words[i] = new WordLetterCount(word));
|
||||||
|
}
|
||||||
|
done.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package fr.neatmonster.nocheatplus.checks.chat.analysis;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Letter count for a word.
|
||||||
|
* @author mc_dev
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public final class WordLetterCount{
|
||||||
|
public final String word;
|
||||||
|
public final Map<Character, Integer> counts;
|
||||||
|
public final int upperCase;
|
||||||
|
public final int notLetter;
|
||||||
|
public WordLetterCount(final String word){
|
||||||
|
this.word = word;
|
||||||
|
char[] a = word.toCharArray();
|
||||||
|
// Preserve insertion order.
|
||||||
|
counts = new LinkedHashMap<Character, Integer>(a.length);
|
||||||
|
int upperCase = 0;
|
||||||
|
int notLetter = 0;
|
||||||
|
for (int i = 0; i < a.length; i++){
|
||||||
|
final char c = a[i];
|
||||||
|
final Character key;
|
||||||
|
if (!Character.isLetter(c)) notLetter ++;
|
||||||
|
if (Character.isUpperCase(c)){
|
||||||
|
upperCase ++;
|
||||||
|
key = Character.toLowerCase(c);
|
||||||
|
}
|
||||||
|
else key = c;
|
||||||
|
final Integer count = counts.remove(key);
|
||||||
|
if (count == null) counts.put(key, 1);
|
||||||
|
else counts.put(key, count.intValue() + 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
this.notLetter = notLetter;
|
||||||
|
this.upperCase = upperCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getNotLetterRatio(){
|
||||||
|
return (float) notLetter / (float) word.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getLetterCountRatio(){
|
||||||
|
return (float) counts.size() / (float) word.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getUpperCaseRatio(){
|
||||||
|
return (float) upperCase / (float) word.length();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package fr.neatmonster.nocheatplus.checks.chat.analysis;
|
||||||
|
|
||||||
|
|
||||||
|
public interface WordProcessor{
|
||||||
|
public String getProcessorName();
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @return A number ranging from 0 to 1. 0 means no matching, 1 means high repetition score.
|
||||||
|
*/
|
||||||
|
public float process(MessageLetterCount message);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user