NoCheatPlus/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/ViolationData.java

395 lines
14 KiB
Java

/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.neatmonster.nocheatplus.checks;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.actions.Action;
import fr.neatmonster.nocheatplus.actions.ActionData;
import fr.neatmonster.nocheatplus.actions.ActionList;
import fr.neatmonster.nocheatplus.actions.ParameterName;
import fr.neatmonster.nocheatplus.actions.types.GenericLogAction;
import fr.neatmonster.nocheatplus.actions.types.PenaltyAction;
import fr.neatmonster.nocheatplus.checks.access.IViolationInfo;
import fr.neatmonster.nocheatplus.compat.BridgeHealth;
import fr.neatmonster.nocheatplus.logging.StaticLog;
import fr.neatmonster.nocheatplus.logging.StreamID;
import fr.neatmonster.nocheatplus.penalties.DefaultPenaltyList;
import fr.neatmonster.nocheatplus.penalties.IPenaltyList;
import fr.neatmonster.nocheatplus.permissions.RegisteredPermission;
/**
* Violation specific data, for executing actions.<br>
* This is meant to capture a violation incident in a potentially thread safe way.
* <hr>
* TODO: Re-think visibility questions.
* @author asofold
*/
public class ViolationData implements IViolationInfo, ActionData {
// TODO:
/** The actions to be executed. */
public final ActionList actions;
/** The actions applicable for the violation level. */
public final Action<ViolationData, ActionList>[] applicableActions;
/** The violation level added. */
public final double addedVL;
/** The check. */
public final Check check;
/** The player. */
public final Player player;
/** The violation level. */
public final double vL;
/** Filled in parameters. */
private Map<ParameterName, String> parameters = null;
private boolean needsParameters = false;
private boolean willCancel;
/** Group penalties by input type. */
private final IPenaltyList penaltyList;
/**
* Instantiates a new violation data without support for input-specific penalties..
* <hr>
* This constructor must be thread-safe for checks that might be executed
* outside of the primary thread.
*
* @param check
* the check
* @param player
* the player
* @param vL
* the violation level
* @param addedVL
* the violation level added
* @param actions
* the actions
*/
public ViolationData(final Check check, final Player player,
final double vL, final double addedVL,
final ActionList actions) {
this(check, player, vL, addedVL, actions, null);
}
/**
* Instantiates a new violation data.
* <hr>
* This constructor must be thread-safe for checks that might be executed
* outside of the primary thread.
*
* @param check
* the check
* @param player
* the player
* @param vL
* the violation level
* @param addedVL
* the violation level added
* @param actions
* the actions
* @param penaltyList
* IPenaltyList instances for filling in, or null to skip, in
* which case a list is created internally.
*/
public ViolationData(final Check check, final Player player,
final double vL, final double addedVL,
final ActionList actions, final IPenaltyList penaltyList) {
this.check = check;
this.player = player;
this.vL = vL;
this.addedVL = addedVL;
this.actions = actions;
this.applicableActions = actions.getActions(vL); // TODO: Consider storing applicableActions only if history wants it.
this.penaltyList = penaltyList == null ? new DefaultPenaltyList() : penaltyList;
boolean needsParameters = false;
for (int i = 0; i < applicableActions.length; i++) {
final Action<ViolationData, ActionList> action = applicableActions[i];
if (!needsParameters && action.needsParameters()) {
needsParameters = true;
}
if (action instanceof PenaltyAction) {
// Note that penalty:action:... or action:penalty:... should be forbidden.
((PenaltyAction<ViolationData, ActionList>) action).evaluate(this.penaltyList);
}
}
if (this.penaltyList.willCancel()) {
// Note: IPenaltyList.isEmpty() might return true, despite willCancel set.
willCancel = true;
}
this.needsParameters = needsParameters;
}
@Override
public boolean willCancel() {
return willCancel;
}
/**
* For penalty evaluation.
*/
public void forceCancel() {
willCancel = true;
}
/**
* Override willCancel.
*/
public void preventCancel() {
willCancel = false;
}
/**
* Gets the actions.
*
* @return the actions
*/
public Action<ViolationData, ActionList> [] getActions() {
return applicableActions;
}
/**
* Execute actions and apply player-specific penalties. Does add to history.
*
* @return
*/
public void executeActions() {
// TODO: Still can return boolean for cancel, as it should've been evaluated on creation of ViolationData.
// TODO: Might be better to return ViolationData, for penalty effects etc.
try {
// Statistics.
ViolationHistory.getHistory(player).log(check.getClass().getName(), addedVL);
// TODO: the time is taken here, which makes sense for delay, but otherwise ?
final long time = System.currentTimeMillis() / 1000L;
// Execute actions, if history wants it. TODO: Consider storing applicableActions only if history wants it.
for (final Action<ViolationData, ActionList> action : applicableActions) {
if (Check.getHistory(player).executeAction(this, action, time)) {
// The execution history said it really is time to execute the action, find out what it is and do
// what is needed.
action.execute(this); // TODO: Add history as argument rather.
}
}
// Apply player penalties here (and remove applied penalties).
if (!penaltyList.isEmpty()) {
penaltyList.applyAllApplicablePenalties(player, true);
}
// PenaltyAction instances.
if (!penaltyList.isEmpty()) {
penaltyList.applyPenaltiesPrecisely(ViolationData.class, this, true);
}
} catch (final Exception e) {
StaticLog.logSevere(e);
// On exceptions cancel events.
willCancel = true;
}
}
/**
* Execute a single action with try-catch and cancel-on-exception. Intended
* for interoperability with penalties.
*
* @param action
*/
public void executeAction(final Action<ViolationData, ActionList> action) {
try {
// Statistics.
ViolationHistory.getHistory(player).log(check.getClass().getName(), addedVL);
final long time = System.currentTimeMillis() / 1000L;
// Execute actions, if history wants it. TODO: Consider storing applicableActions only if history wants it.
if (Check.getHistory(player).executeAction(this, action, time)) {
action.execute(this);
}
}
catch (final Exception e) {
StaticLog.logSevere(e);
// On exceptions cancel events.
willCancel = true;
}
}
@Override
@Deprecated
public boolean hasCancel() {
return willCancel();
}
@Override
public String getParameter(final ParameterName parameterName) {
if (parameterName == null) {
return "<???>";
}
String value = null;
if (parameters != null) {
value = parameters.get(parameterName);
}
if (value == null) {
value = getImplicitValue(parameterName);
}
// If null, return what would have been parsed to get the parameter.
return (value == null) ? ("[" + parameterName.getText() + "]") : value;
}
/**
* Evaluate parameters that are supported to be fetched, even if no
* parameters have been set explicitly.
*
* @param parameterName
* @return
*/
private String getImplicitValue(final ParameterName parameterName) {
switch (parameterName) {
case CHECK:
return check.getClass().getSimpleName();
case HEALTH: {
// Not optimal to get dynamically if the ViolationData is created off the primary thread.
String health = getParameterValue(ParameterName.HEALTH);
return health == null ? (BridgeHealth.getHealth(player) + "/" + BridgeHealth.getMaxHealth(player)) : health;
}
case IP:
return player.getAddress().toString().substring(1).split(":")[0];
case PLAYER:
case PLAYER_NAME:
return player.getName();
case PLAYER_DISPLAY_NAME:
return player.getDisplayName();
case UUID:
return player.getUniqueId().toString();
case VIOLATIONS:
return String.valueOf((long) Math.round(vL));
case WORLD: {
String world = getParameterValue(ParameterName.WORLD);
return world == null ? player.getWorld().getName() : world;
}
default:
return null;
}
}
private String getParameterValue(ParameterName parameterName) {
return parameters == null ? null : parameters.get(parameterName);
}
@Override
public void setParameter(final ParameterName parameterName, final String value) {
if (parameters == null) {
parameters = new HashMap<ParameterName, String>();
}
parameters.put(parameterName, value);
}
/**
* Convenience method: Delegates to setParameter, return this for chaining.
*
* @param parameterName
* @param value
* @return This ViolationData instance.
*/
public ViolationData chainParameter(final ParameterName parameterName, final String value) {
setParameter(parameterName, value);
return this;
}
@Override
public boolean needsParameters() {
return needsParameters;
}
@Override
public boolean hasParameters() {
return parameters != null && !parameters.isEmpty();
}
@Override
public double getAddedVl() {
return addedVL;
}
@Override
public double getTotalVl() {
return vL;
}
public RegisteredPermission getPermissionSilent() {
return actions.permissionSilent;
}
public ActionList getActionList() {
return actions;
}
/**
* Test if there exist log actions in applicableActions that might log to
* the referenced stream. This method will iterate over applicableActions,
* it's not very efficient to call it multiple times (unless for JIT).
* Usually actions are stored in 'optimized' form, thus this result should
* be final. If an action is not stored in optimized form, it might still
* check for configuration flags on execute (bugs or custom actions
* implementations).
*
* @param streamID
* @return
*/
public boolean logsToStream(final StreamID streamID) {
if (!needsParameters) {
return false;
}
for (final Action <ViolationData, ActionList> action : applicableActions) {
if ((action instanceof GenericLogAction) && ((GenericLogAction) action).logsToStream(streamID)) {
return true;
}
}
return false;
}
/**
* Test if there are any instances of GenericLogAction in the
* applicableActions. This method will iterate over applicableActions, it's
* not efficient to call it extra to logsToStream. Usually actions are
* stored in 'optimized' form, thus this result should be final. If an
* action is not stored in optimized form, it might still check for
* configuration flags on execute (bugs or custom actions implementations).
*
* @return
*/
public boolean hasLogAction() {
if (!needsParameters) {
return false;
}
for (final Action <ViolationData, ActionList> action : applicableActions) {
if (action instanceof GenericLogAction) {
return true;
}
}
return false;
}
}