More spaces.

This commit is contained in:
asofold 2014-11-04 00:20:18 +01:00
parent 8b43e5e5f1
commit 1f2f18a748
4 changed files with 331 additions and 332 deletions

View File

@ -13,7 +13,6 @@ import fr.neatmonster.nocheatplus.compat.MCAccess;
import fr.neatmonster.nocheatplus.components.MCAccessHolder; import fr.neatmonster.nocheatplus.components.MCAccessHolder;
import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager; import fr.neatmonster.nocheatplus.hooks.NCPExemptionManager;
import fr.neatmonster.nocheatplus.hooks.NCPHookManager; import fr.neatmonster.nocheatplus.hooks.NCPHookManager;
import fr.neatmonster.nocheatplus.logging.LogUtil;
import fr.neatmonster.nocheatplus.players.DataManager; import fr.neatmonster.nocheatplus.players.DataManager;
import fr.neatmonster.nocheatplus.players.ExecutionHistory; import fr.neatmonster.nocheatplus.players.ExecutionHistory;
import fr.neatmonster.nocheatplus.utilities.TickTask; import fr.neatmonster.nocheatplus.utilities.TickTask;
@ -41,7 +40,7 @@ public abstract class Check implements MCAccessHolder{
/** The type. */ /** The type. */
protected final CheckType type; protected final CheckType type;
protected MCAccess mcAccess; protected MCAccess mcAccess;
/** /**
@ -56,7 +55,7 @@ public abstract class Check implements MCAccessHolder{
ViolationHistory.checkTypeMap.put(getClass().getName(), type); ViolationHistory.checkTypeMap.put(getClass().getName(), type);
DataManager.registerExecutionHistory(type, histories); DataManager.registerExecutionHistory(type, histories);
} }
/** /**
* Execute actions, possibly thread safe according to the isMainThread flag.<br> * Execute actions, possibly thread safe according to the isMainThread flag.<br>
* This does not use extra synchronization. * This does not use extra synchronization.
@ -94,7 +93,7 @@ public abstract class Check implements MCAccessHolder{
final ActionList actions) { final ActionList actions) {
return executeActions(new ViolationData(this, player, vL, addedVL, actions), true); return executeActions(new ViolationData(this, player, vL, addedVL, actions), true);
} }
/** /**
* Execute some actions for the specified player, only for the main thread. * Execute some actions for the specified player, only for the main thread.
* *
@ -103,7 +102,7 @@ public abstract class Check implements MCAccessHolder{
* @return true, if the event should be cancelled * @return true, if the event should be cancelled
*/ */
protected boolean executeActions(final ViolationData violationData){ protected boolean executeActions(final ViolationData violationData){
return executeActions(violationData, true); return executeActions(violationData, true);
} }
/** /**
@ -115,24 +114,24 @@ public abstract class Check implements MCAccessHolder{
* @return true, if the event should be cancelled * @return true, if the event should be cancelled
*/ */
protected boolean executeActions(final ViolationData violationData, final boolean isMainThread) { protected boolean executeActions(final ViolationData violationData, final boolean isMainThread) {
// Dispatch the VL processing to the hook manager (now thread safe). // Dispatch the VL processing to the hook manager (now thread safe).
if (NCPHookManager.shouldCancelVLProcessing(violationData)) if (NCPHookManager.shouldCancelVLProcessing(violationData))
// One of the hooks has decided to cancel the VL processing, return false. // One of the hooks has decided to cancel the VL processing, return false.
return false; return false;
final boolean hasCancel = violationData.hasCancel(); final boolean hasCancel = violationData.hasCancel();
if (isMainThread) if (isMainThread)
return violationData.executeActions(); return violationData.executeActions();
else else
// Always schedule to add to ViolationHistory. // Always schedule to add to ViolationHistory.
TickTask.requestActionsExecution(violationData); TickTask.requestActionsExecution(violationData);
// (Design change: Permission checks are moved to cached permissions, lazily updated.) // (Design change: Permission checks are moved to cached permissions, lazily updated.)
return hasCancel; return hasCancel;
} }
/** /**
* Fill in parameters for creating violation data. * Fill in parameters for creating violation data.
* Individual checks should override this to fill in check specific parameters, * Individual checks should override this to fill in check specific parameters,
@ -141,9 +140,9 @@ public abstract class Check implements MCAccessHolder{
* @return * @return
*/ */
protected Map<ParameterName, String> getParameterMap(final ViolationData violationData){ protected Map<ParameterName, String> getParameterMap(final ViolationData violationData){
final Map<ParameterName, String> params = new HashMap<ParameterName, String>(); final Map<ParameterName, String> params = new HashMap<ParameterName, String>();
// (Standard parameters like player, vl, check name are filled in in ViolationData.getParameter!) // (Standard parameters like player, vl, check name are filled in in ViolationData.getParameter!)
return params; return params;
} }
/** /**
@ -180,14 +179,14 @@ public abstract class Check implements MCAccessHolder{
return !NCPExemptionManager.isExempted(player, type); return !NCPExemptionManager.isExempted(player, type);
} }
@Override @Override
public void setMCAccess(MCAccess mcAccess) { public void setMCAccess(MCAccess mcAccess) {
this.mcAccess = mcAccess; this.mcAccess = mcAccess;
} }
@Override
public MCAccess getMCAccess() {
return mcAccess;
}
@Override
public MCAccess getMCAccess() {
return mcAccess;
}
} }

View File

@ -12,13 +12,13 @@ public abstract class ACheckConfig implements ICheckConfig {
/** For on the fly debug setting. */ /** For on the fly debug setting. */
public boolean debug = false; public boolean debug = false;
/** If to adapt to server side lag. */ /** If to adapt to server side lag. */
public final boolean lag; public final boolean lag;
/** Permissions to hold in player data cache, not final for flexibility. */ /** Permissions to hold in player data cache, not final for flexibility. */
protected String[] cachePermissions; protected String[] cachePermissions;
/** /**
* *
* @param config * @param config
@ -26,8 +26,8 @@ public abstract class ACheckConfig implements ICheckConfig {
*/ */
public ACheckConfig(final ConfigFile config, final String pathPrefix){ public ACheckConfig(final ConfigFile config, final String pathPrefix){
this(config, pathPrefix, null); this(config, pathPrefix, null);
} }
/** /**
* *
* @param config * @param config
@ -41,11 +41,11 @@ public abstract class ACheckConfig implements ICheckConfig {
lag = config.getBoolean(pathPrefix + ConfPaths.SUB_LAG, true) && config.getBoolean(ConfPaths.MISCELLANEOUS_LAG, true); lag = config.getBoolean(pathPrefix + ConfPaths.SUB_LAG, true) && config.getBoolean(ConfPaths.MISCELLANEOUS_LAG, true);
this.cachePermissions = cachePermissions; this.cachePermissions = cachePermissions;
} }
@Override @Override
public String[] getCachePermissions() { public String[] getCachePermissions() {
return cachePermissions; return cachePermissions;
} }
@Override @Override
public boolean getDebug() { public boolean getDebug() {
@ -56,5 +56,5 @@ public abstract class ACheckConfig implements ICheckConfig {
public void setDebug(final boolean debug) { public void setDebug(final boolean debug) {
this.debug = debug; this.debug = debug;
} }
} }

View File

@ -15,25 +15,25 @@ import fr.neatmonster.nocheatplus.utilities.ColorUtil;
* *
*/ */
public class Captcha extends Check implements ICaptcha{ public class Captcha extends Check implements ICaptcha{
public Captcha() {
super(CheckType.CHAT_CAPTCHA);
}
/** The random number generator. */ public Captcha() {
super(CheckType.CHAT_CAPTCHA);
}
/** The random number generator. */
private final Random random = new Random(); private final Random random = new Random();
@Override @Override
public void checkCaptcha(Player player, String message, ChatConfig cc, ChatData data, boolean isMainThread) { public void checkCaptcha(Player player, String message, ChatConfig cc, ChatData data, boolean isMainThread) {
// Correct answer to the captcha? // Correct answer to the captcha?
if (message.equals(data.captchaGenerated)) { if (message.equals(data.captchaGenerated)) {
// Yes, clear their data and do not worry anymore about them. // Yes, clear their data and do not worry anymore about them.
data.reset(); data.reset();
data.captchaStarted = false; data.captchaStarted = false;
player.sendMessage(ColorUtil.replaceColors(cc.captchaSuccess)); player.sendMessage(ColorUtil.replaceColors(cc.captchaSuccess));
} else { } else {
// Increment their tries number counter. // Increment their tries number counter.
data.captchTries++; data.captchTries++;
data.captchaVL ++; data.captchaVL ++;
// Have they failed too man times? // Have they failed too man times?
@ -45,58 +45,58 @@ public class Captcha extends Check implements ICaptcha{
// Display the question again (if not kicked). // Display the question again (if not kicked).
if (player.isOnline()) { if (player.isOnline()) {
sendCaptcha(player, cc, data); sendCaptcha(player, cc, data);
} }
} }
} }
@Override @Override
public void sendNewCaptcha(Player player, ChatConfig cc, ChatData data) { public void sendNewCaptcha(Player player, ChatConfig cc, ChatData data) {
// Display a captcha to the player. // Display a captcha to the player.
generateCaptcha(cc, data, true); generateCaptcha(cc, data, true);
sendCaptcha(player, cc, data); sendCaptcha(player, cc, data);
data.captchaStarted = true; data.captchaStarted = true;
} }
@Override @Override
public void generateCaptcha(ChatConfig cc, ChatData data, boolean reset) { public void generateCaptcha(ChatConfig cc, ChatData data, boolean reset) {
if (reset) data.captchTries = 0; if (reset) data.captchTries = 0;
final char[] chars = new char[cc.captchaLength]; final char[] chars = new char[cc.captchaLength];
for (int i = 0; i < cc.captchaLength; i++) for (int i = 0; i < cc.captchaLength; i++)
chars[i] = cc.captchaCharacters.charAt(random chars[i] = cc.captchaCharacters.charAt(random
.nextInt(cc.captchaCharacters.length())); .nextInt(cc.captchaCharacters.length()));
data.captchaGenerated = new String(chars); data.captchaGenerated = new String(chars);
} }
@Override @Override
public void resetCaptcha(Player player){ public void resetCaptcha(Player player){
ChatData data = ChatData.getData(player); ChatData data = ChatData.getData(player);
synchronized (data) { synchronized (data) {
resetCaptcha(ChatConfig.getConfig(player), data); resetCaptcha(ChatConfig.getConfig(player), data);
} }
} }
@Override @Override
public void resetCaptcha(ChatConfig cc, ChatData data){ public void resetCaptcha(ChatConfig cc, ChatData data){
data.captchTries = 0; data.captchTries = 0;
if (shouldCheckCaptcha(cc, data) || shouldStartCaptcha(cc, data)){ if (shouldCheckCaptcha(cc, data) || shouldStartCaptcha(cc, data)){
generateCaptcha(cc, data, true); generateCaptcha(cc, data, true);
} }
} }
@Override @Override
public void sendCaptcha(Player player, ChatConfig cc, ChatData data) { public void sendCaptcha(Player player, ChatConfig cc, ChatData data) {
player.sendMessage(ColorUtil.replaceColors(cc.captchaQuestion.replace("[captcha]", player.sendMessage(ColorUtil.replaceColors(cc.captchaQuestion.replace("[captcha]",
data.captchaGenerated))); data.captchaGenerated)));
} }
@Override @Override
public boolean shouldStartCaptcha(ChatConfig cc, ChatData data) { public boolean shouldStartCaptcha(ChatConfig cc, ChatData data) {
return cc.captchaCheck && !data.captchaStarted && !data.hasCachedPermission(Permissions.CHAT_CAPTCHA); return cc.captchaCheck && !data.captchaStarted && !data.hasCachedPermission(Permissions.CHAT_CAPTCHA);
} }
@Override @Override
public boolean shouldCheckCaptcha(ChatConfig cc, ChatData data) { public boolean shouldCheckCaptcha(ChatConfig cc, ChatData data) {
return cc.captchaCheck && data.captchaStarted && !data.hasCachedPermission(Permissions.CHAT_CAPTCHA); return cc.captchaCheck && data.captchaStarted && !data.hasCachedPermission(Permissions.CHAT_CAPTCHA);
} }
} }

View File

@ -31,140 +31,140 @@ import fr.neatmonster.nocheatplus.utilities.StringUtil;
*/ */
public class Text extends Check implements INotifyReload { public class Text extends Check implements INotifyReload {
private LetterEngine engine = null; private LetterEngine engine = null;
/** Not really cancelled but above threshold for actions. */
private String lastCancelledMessage = "";
private long lastCancelledTime = 0;
private String lastGlobalMessage = "";
private long lastGlobalTime = 0;
public Text() {
super(CheckType.CHAT_TEXT);
init();
}
/** /** Not really cancelled but above threshold for actions. */
* Start analysis. private String lastCancelledMessage = "";
* @param player private long lastCancelledTime = 0;
* The player who issued the message.
* @param message
* The message to check.
* @param captcha
* Used for starting captcha on failure, if configured so.
* @param alreadyCancelled
* @return
*/
public boolean check(final Player player, final String message, final ICaptcha captcha, boolean isMainThread, final boolean alreadyCancelled) {
final ChatConfig cc = ChatConfig.getConfig(player);
final ChatData data = ChatData.getData(player);
synchronized (data) {
return unsafeCheck(player, message, captcha, cc, data, isMainThread, alreadyCancelled);
}
}
private void init() {
// Set some things from the global config.
final ConfigFile config = ConfigManager.getConfigFile();
final NoCheatPlusAPI api = NCPAPIProvider.getNoCheatPlusAPI();
if (engine != null) {
engine.clear();
api.removeComponent(engine);
}
engine = new LetterEngine(config);
api.addComponent(engine);
}
@Override private String lastGlobalMessage = "";
public void onReload() { private long lastGlobalTime = 0;
synchronized(engine) {
engine.clear();
}
init();
}
/** public Text() {
* Check without further synchronization. super(CheckType.CHAT_TEXT);
* @param player init();
* @param message }
* @param captcha
* @param cc /**
* @param data * Start analysis.
* @param isMainThread * @param player
* @param alreadyCancelled * The player who issued the message.
* @return * @param message
*/ * The message to check.
private boolean unsafeCheck(final Player player, final String message, final ICaptcha captcha, * @param captcha
final ChatConfig cc, final ChatData data, boolean isMainThread, final boolean alreadyCancelled) { * Used for starting captcha on failure, if configured so.
* @param alreadyCancelled
// Test captcha. * @return
// TODO: Skip captcha for "handleaschat" commands? [controversial potential] */
if (captcha.shouldCheckCaptcha(cc, data)) { public boolean check(final Player player, final String message, final ICaptcha captcha, boolean isMainThread, final boolean alreadyCancelled) {
captcha.checkCaptcha(player, message, cc, data, isMainThread);
return true; final ChatConfig cc = ChatConfig.getConfig(player);
} else if (alreadyCancelled) { final ChatData data = ChatData.getData(player);
// Skip checking.
return true; synchronized (data) {
} return unsafeCheck(player, message, captcha, cc, data, isMainThread, alreadyCancelled);
}
// Take time once: }
final long time = System.currentTimeMillis();
private void init() {
final String lcMessage = message.trim().toLowerCase(); // Set some things from the global config.
final ConfigFile config = ConfigManager.getConfigFile();
boolean cancel = false; final NoCheatPlusAPI api = NCPAPIProvider.getNoCheatPlusAPI();
if (engine != null) {
boolean debug = cc.textDebug || cc.debug; engine.clear();
api.removeComponent(engine);
final List<String> debugParts; }
if (debug) { engine = new LetterEngine(config);
debugParts = new LinkedList<String>(); api.addComponent(engine);
debugParts.add("[NoCheatPlus][chat.text] Message ("+player.getName()+"/"+message.length()+"): "); }
}
else debugParts = null; @Override
public void onReload() {
// Update the frequency interval weights. synchronized(engine) {
data.chatFrequency.update(time); engine.clear();
}
// Score for this message (violation score). init();
float score = 0; }
final MessageLetterCount letterCounts = new MessageLetterCount(message); /**
* Check without further synchronization.
final int msgLen = message.length(); * @param player
* @param message
// (Following: random/made up criteria.) * @param captcha
* @param cc
// TODO: Create tests for all methods with wordlists, fake chat (refactor for that). * @param data
* @param isMainThread
// Full message processing. ------------ * @param alreadyCancelled
* @return
// Upper case. */
if (letterCounts.fullCount.upperCase > msgLen / 3) { private boolean unsafeCheck(final Player player, final String message, final ICaptcha captcha,
final float wUpperCase = 0.6f * letterCounts.fullCount.getUpperCaseRatio(); final ChatConfig cc, final ChatData data, boolean isMainThread, final boolean alreadyCancelled) {
score += wUpperCase * cc.textMessageUpperCase;
} // Test captcha.
// TODO: Skip captcha for "handleaschat" commands? [controversial potential]
// Letters vs. word length. if (captcha.shouldCheckCaptcha(cc, data)) {
if (msgLen > 4) { captcha.checkCaptcha(player, message, cc, data, isMainThread);
final float fullRep = letterCounts.fullCount.getLetterCountRatio(); return true;
// Long messages: very small and very big are bad ! } else if (alreadyCancelled) {
final float wRepetition = (float) msgLen / 15.0f * Math.abs(0.5f - fullRep); // Skip checking.
score += wRepetition * cc.textMessageLetterCount; return true;
}
// Number of words vs. length of message
final float fnWords = (float) letterCounts.words.length / (float) msgLen; // Take time once:
if (fnWords > 0.75f) { // TODO: balance or configure or remove ? final long time = System.currentTimeMillis();
score += fnWords * cc.textMessagePartition;
} final String lcMessage = message.trim().toLowerCase();
}
boolean cancel = false;
final CombinedData cData = CombinedData.getData(player);
final long timeout = 8000; // TODO: maybe set dynamically in data. boolean debug = cc.textDebug || cc.debug;
// Repetition of last message.
final List<String> debugParts;
if (debug) {
debugParts = new LinkedList<String>();
debugParts.add("[NoCheatPlus][chat.text] Message ("+player.getName()+"/"+message.length()+"): ");
}
else debugParts = null;
// Update the frequency interval weights.
data.chatFrequency.update(time);
// Score for this message (violation score).
float score = 0;
final MessageLetterCount letterCounts = new MessageLetterCount(message);
final int msgLen = message.length();
// (Following: random/made up criteria.)
// TODO: Create tests for all methods with wordlists, fake chat (refactor for that).
// Full message processing. ------------
// Upper case.
if (letterCounts.fullCount.upperCase > msgLen / 3) {
final float wUpperCase = 0.6f * letterCounts.fullCount.getUpperCaseRatio();
score += wUpperCase * cc.textMessageUpperCase;
}
// Letters vs. word length.
if (msgLen > 4) {
final float fullRep = letterCounts.fullCount.getLetterCountRatio();
// Long messages: very small and very big are bad !
final float wRepetition = (float) msgLen / 15.0f * Math.abs(0.5f - fullRep);
score += wRepetition * cc.textMessageLetterCount;
// Number of words vs. length of message
final float fnWords = (float) letterCounts.words.length / (float) msgLen;
if (fnWords > 0.75f) { // TODO: balance or configure or remove ?
score += fnWords * cc.textMessagePartition;
}
}
final CombinedData cData = CombinedData.getData(player);
final long timeout = 8000; // TODO: maybe set dynamically in data.
// Repetition of last message.
if (cc.textMsgRepeatSelf != 0f && time - data.chatLastTime < timeout) { if (cc.textMsgRepeatSelf != 0f && time - data.chatLastTime < timeout) {
if (StringUtil.isSimilar(lcMessage, data.chatLastMessage, 0.8f)) { if (StringUtil.isSimilar(lcMessage, data.chatLastMessage, 0.8f)) {
final float timeWeight = (float) (timeout - (time - data.chatLastTime)) / (float) timeout; final float timeWeight = (float) (timeout - (time - data.chatLastTime)) / (float) timeout;
@ -179,147 +179,147 @@ public class Text extends Check implements INotifyReload {
} }
} }
// Repetition of last cancelled message. // Repetition of last cancelled message.
if (cc.textMsgRepeatCancel != 0f && time - lastCancelledTime < timeout) { if (cc.textMsgRepeatCancel != 0f && time - lastCancelledTime < timeout) {
if (StringUtil.isSimilar(lcMessage, lastCancelledMessage, 0.8f)) { if (StringUtil.isSimilar(lcMessage, lastCancelledMessage, 0.8f)) {
final float timeWeight = (float) (timeout - (time - lastCancelledTime)) / (float) timeout; final float timeWeight = (float) (timeout - (time - lastCancelledTime)) / (float) timeout;
score += cc.textMsgRepeatCancel * timeWeight; score += cc.textMsgRepeatCancel * timeWeight;
} }
} }
// Chat quickly after join. // Chat quickly after join.
if (cc.textMsgAfterJoin != 0f && time - cData.lastJoinTime < timeout) { if (cc.textMsgAfterJoin != 0f && time - cData.lastJoinTime < timeout) {
final float timeWeight = (float) (timeout - (time - cData.lastJoinTime)) / (float) timeout; final float timeWeight = (float) (timeout - (time - cData.lastJoinTime)) / (float) timeout;
score += cc.textMsgAfterJoin * timeWeight; score += cc.textMsgAfterJoin * timeWeight;
} }
// Chat without moving. // Chat without moving.
if (cc.textMsgNoMoving != 0f && time - cData.lastMoveTime > timeout) { if (cc.textMsgNoMoving != 0f && time - cData.lastMoveTime > timeout) {
score += cc.textMsgNoMoving; score += cc.textMsgNoMoving;
} }
// Per word checks. -------------------
float wWords = 0.0f;
final float avwLen = (float) msgLen / (float) letterCounts.words.length;
for (final WordLetterCount word: letterCounts.words) {
float wWord = 0.0f;
final int wLen = word.word.length();
// TODO: ? used letters vs. word length.
// Length of word vs. av. word length.
final float fLenAv = Math.abs(avwLen - (float) wLen) / avwLen;
wWord += fLenAv * cc.textMessageLengthAv;
// Length of word vs. message length;
final float fLenMsg = (float) wLen / (float) msgLen;
wWord += fLenMsg * cc.textMessageLengthMsg;
// Not letter:
float notLetter = word.getNotLetterRatio();
notLetter *= notLetter;
wWord += notLetter * cc.textMessageNoLetter;
wWord *= wWord; // TODO: quadratic ? (configurable)
wWords += wWord;
}
wWords /= (float) letterCounts.words.length;
score += wWords;
if (debug && score > 0f) debugParts.add("Simple score: " + StringUtil.fdec3.format(score));
// Engine:
// 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.textEngineMaximum) wEngine = Math.max(wEngine, res.floatValue());
else wEngine += res.floatValue();
}
}
score += wEngine;
// Wrapping it up. --------------------
// Add weight to frequency counts.
final float normalScore = Math.max(cc.textFreqNormMin, score);
data.chatFrequency.add(time, normalScore);
final float accumulated = cc.textFreqNormWeight * data.chatFrequency.score(cc.textFreqNormFactor);
final boolean normalViolation = accumulated > cc.textFreqNormLevel;
final float shortTermScore = Math.max(cc.textFreqShortTermMin, score); // Per word checks. -------------------
data.chatShortTermFrequency.add(time, shortTermScore); float wWords = 0.0f;
// TODO: very short term (1st bucket) or do it indirectly. final float avwLen = (float) msgLen / (float) letterCounts.words.length;
final float shortTermAccumulated = cc.textFreqShortTermWeight * data.chatShortTermFrequency.score(cc.textFreqShortTermFactor); for (final WordLetterCount word: letterCounts.words) {
final boolean shortTermViolation = shortTermAccumulated > cc.textFreqShortTermLevel; float wWord = 0.0f;
final int wLen = word.word.length();
if (normalViolation || shortTermViolation) { // TODO: ? used letters vs. word length.
lastCancelledMessage = lcMessage;
lastCancelledTime = time; // Length of word vs. av. word length.
final float fLenAv = Math.abs(avwLen - (float) wLen) / avwLen;
final double added; wWord += fLenAv * cc.textMessageLengthAv;
if (shortTermViolation) {
added = (shortTermAccumulated - cc.textFreqShortTermLevel)/ 3.0; // Length of word vs. message length;
} else { final float fLenMsg = (float) wLen / (float) msgLen;
added = (accumulated - cc.textFreqNormLevel) / 10.0; wWord += fLenMsg * cc.textMessageLengthMsg;
}
data.textVL += added; // Not letter:
float notLetter = word.getNotLetterRatio();
if (captcha.shouldStartCaptcha(cc, data)) { notLetter *= notLetter;
captcha.sendNewCaptcha(player, cc, data); wWord += notLetter * cc.textMessageNoLetter;
cancel = true;
} wWord *= wWord; // TODO: quadratic ? (configurable)
else{ wWords += wWord;
if (shortTermViolation) { }
wWords /= (float) letterCounts.words.length;
score += wWords;
if (debug && score > 0f) debugParts.add("Simple score: " + StringUtil.fdec3.format(score));
// Engine:
// 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.textEngineMaximum) wEngine = Math.max(wEngine, res.floatValue());
else wEngine += res.floatValue();
}
}
score += wEngine;
// Wrapping it up. --------------------
// Add weight to frequency counts.
final float normalScore = Math.max(cc.textFreqNormMin, score);
data.chatFrequency.add(time, normalScore);
final float accumulated = cc.textFreqNormWeight * data.chatFrequency.score(cc.textFreqNormFactor);
final boolean normalViolation = accumulated > cc.textFreqNormLevel;
final float shortTermScore = Math.max(cc.textFreqShortTermMin, score);
data.chatShortTermFrequency.add(time, shortTermScore);
// TODO: very short term (1st bucket) or do it indirectly.
final float shortTermAccumulated = cc.textFreqShortTermWeight * data.chatShortTermFrequency.score(cc.textFreqShortTermFactor);
final boolean shortTermViolation = shortTermAccumulated > cc.textFreqShortTermLevel;
if (normalViolation || shortTermViolation) {
lastCancelledMessage = lcMessage;
lastCancelledTime = time;
final double added;
if (shortTermViolation) {
added = (shortTermAccumulated - cc.textFreqShortTermLevel)/ 3.0;
} else {
added = (accumulated - cc.textFreqNormLevel) / 10.0;
}
data.textVL += added;
if (captcha.shouldStartCaptcha(cc, data)) {
captcha.sendNewCaptcha(player, cc, data);
cancel = true;
}
else{
if (shortTermViolation) {
if (executeActions(player, data.textVL, added, cc.textFreqShortTermActions, isMainThread)) { if (executeActions(player, data.textVL, added, cc.textFreqShortTermActions, isMainThread)) {
cancel = true; cancel = true;
} }
} }
else if (normalViolation) { else if (normalViolation) {
if (executeActions(player, data.textVL, added, cc.textFreqNormActions, isMainThread)) { if (executeActions(player, data.textVL, added, cc.textFreqNormActions, isMainThread)) {
cancel = true; cancel = true;
} }
} }
} }
} }
else if (cc.chatWarningCheck && time - data.chatWarningTime > cc.chatWarningTimeout && (100f * accumulated / cc.textFreqNormLevel > cc.chatWarningLevel || 100f * shortTermAccumulated / cc.textFreqShortTermLevel > cc.chatWarningLevel)) { else if (cc.chatWarningCheck && time - data.chatWarningTime > cc.chatWarningTimeout && (100f * accumulated / cc.textFreqNormLevel > cc.chatWarningLevel || 100f * shortTermAccumulated / cc.textFreqShortTermLevel > cc.chatWarningLevel)) {
NCPAPIProvider.getNoCheatPlusAPI().sendMessageOnTick(player.getName(), ColorUtil.replaceColors(cc.chatWarningMessage)); NCPAPIProvider.getNoCheatPlusAPI().sendMessageOnTick(player.getName(), ColorUtil.replaceColors(cc.chatWarningMessage));
data.chatWarningTime = time; data.chatWarningTime = time;
} }
else { else {
data.textVL *= 0.95; data.textVL *= 0.95;
if (cc.textAllowVLReset && normalScore < 2.0f * cc.textFreqNormWeight && shortTermScore < 2.0f * cc.textFreqShortTermWeight) { if (cc.textAllowVLReset && normalScore < 2.0f * cc.textFreqNormWeight && shortTermScore < 2.0f * cc.textFreqShortTermWeight) {
// Reset the VL. // Reset the VL.
// TODO: maybe elaborate on resetting conditions (after some timeout just divide by two or so?). // TODO: maybe elaborate on resetting conditions (after some timeout just divide by two or so?).
data.textVL = 0.0; data.textVL = 0.0;
} }
} }
if (debug) { if (debug) {
final List<String> keys = new LinkedList<String>(engMap.keySet()); final List<String> keys = new LinkedList<String>(engMap.keySet());
Collections.sort(keys); Collections.sort(keys);
for (String key : keys) { for (String key : keys) {
Float s = engMap.get(key); Float s = engMap.get(key);
if (s.floatValue() > 0.0f) if (s.floatValue() > 0.0f)
debugParts.add(key + ":" + StringUtil.fdec3.format(s)); debugParts.add(key + ":" + StringUtil.fdec3.format(s));
} }
if (wEngine > 0.0f) if (wEngine > 0.0f)
debugParts.add("Engine score (" + (cc.textEngineMaximum?"max":"sum") + "): " + StringUtil.fdec3.format(wEngine)); debugParts.add("Engine score (" + (cc.textEngineMaximum?"max":"sum") + "): " + StringUtil.fdec3.format(wEngine));
debugParts.add("Final score: " + StringUtil.fdec3.format(score)); debugParts.add("Final score: " + StringUtil.fdec3.format(score));
debugParts.add("Normal: min=" + StringUtil.fdec3.format(cc.textFreqNormMin) +", weight=" + StringUtil.fdec3.format(cc.textFreqNormWeight) + " => accumulated=" + StringUtil.fdec3.format(accumulated)); debugParts.add("Normal: min=" + StringUtil.fdec3.format(cc.textFreqNormMin) +", weight=" + StringUtil.fdec3.format(cc.textFreqNormWeight) + " => accumulated=" + StringUtil.fdec3.format(accumulated));
debugParts.add("Short-term: min=" + StringUtil.fdec3.format(cc.textFreqShortTermMin) +", weight=" + StringUtil.fdec3.format(cc.textFreqShortTermWeight) + " => accumulated=" + StringUtil.fdec3.format(shortTermAccumulated)); debugParts.add("Short-term: min=" + StringUtil.fdec3.format(cc.textFreqShortTermMin) +", weight=" + StringUtil.fdec3.format(cc.textFreqShortTermWeight) + " => accumulated=" + StringUtil.fdec3.format(shortTermAccumulated));
debugParts.add("vl: " + StringUtil.fdec3.format(data.textVL)); debugParts.add("vl: " + StringUtil.fdec3.format(data.textVL));
LogUtil.scheduleLogInfo(debugParts, " | "); LogUtil.scheduleLogInfo(debugParts, " | ");
debugParts.clear(); debugParts.clear();
} }
lastGlobalMessage = data.chatLastMessage = lcMessage; lastGlobalMessage = data.chatLastMessage = lcMessage;
lastGlobalTime = data.chatLastTime = time; lastGlobalTime = data.chatLastTime = time;
return cancel; return cancel;
} }
@Override @Override
protected Map<ParameterName, String> getParameterMap(final ViolationData violationData) { protected Map<ParameterName, String> getParameterMap(final ViolationData violationData) {
final Map<ParameterName, String> parameters = super.getParameterMap(violationData); final Map<ParameterName, String> parameters = super.getParameterMap(violationData);
parameters.put(ParameterName.IP, violationData.player.getAddress().toString().substring(1).split(":")[0]); parameters.put(ParameterName.IP, violationData.player.getAddress().toString().substring(1).split(":")[0]);