mirror of
https://github.com/NoCheatPlus/NoCheatPlus.git
synced 2024-09-05 03:31:09 +02:00
More spaces.
This commit is contained in:
parent
8b43e5e5f1
commit
1f2f18a748
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user