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. */
|
||||
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];
|
||||
|
||||
/** 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.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;
|
||||
|
||||
/**
|
||||
@ -14,6 +16,8 @@ import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;
|
||||
*/
|
||||
public class GlobalChat extends Check{
|
||||
|
||||
private final LetterEngine engine = new LetterEngine();
|
||||
|
||||
public GlobalChat() {
|
||||
super(CheckType.CHAT_GLOBALCHAT);
|
||||
}
|
||||
@ -79,6 +83,8 @@ public class GlobalChat extends Check{
|
||||
|
||||
// (Following: random/made up criteria.)
|
||||
|
||||
// TODO: Create tests for all methods with wordlists, fake chat (refactor for that).
|
||||
|
||||
// Full message processing. ------------
|
||||
|
||||
// Upper case.
|
||||
@ -128,7 +134,9 @@ public class GlobalChat extends Check{
|
||||
wWords /= (float) letterCounts.words.length;
|
||||
score += wWords;
|
||||
|
||||
// TODO: LetterEngine ? + core checks
|
||||
// Engine:
|
||||
final float wEngine = engine.feed(letterCounts);
|
||||
score += wEngine;
|
||||
|
||||
// Wrapping it up. --------------------
|
||||
// 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