Rough sketch of workaround confinement and statistics support.

Untested, unused. Intentions are:
* Be able to count any use of workarounds.
* Confine workarounds to side conditions, such as 'use once until
conditions are reset' and/or 'only use once conditions are set'.
* Have per-player objects and (attached) global counters.
* (Might think of: disable workarounds by configuration.)
This commit is contained in:
asofold 2016-01-17 02:08:42 +01:00
parent 027079320e
commit 567ef1b971
9 changed files with 602 additions and 0 deletions

View File

@ -0,0 +1,62 @@
package fr.neatmonster.nocheatplus.workaround;
/**
* Implementing the minimum features for counting use, plus the ability to trigger a parent
* count.
*
* @author asofold
*
*/
public abstract class AbstractWorkaround implements Workaround {
private final String id;
private final Workaround parent;
private int useCount = 0;
public AbstractWorkaround(String id) {
this(id, null); // No parent.
}
/**
*
* @param id
* @param parent
* For (global) count: parent.use() is called from within
* this.use(), but the result is not evaluate.
*/
public AbstractWorkaround(String id, Workaround parent) {
this.id = id;
this.parent = parent;
}
public Workaround getParent() {
return parent;
}
@Override
public String getId() {
return id;
}
@Override
public int getUseCount() {
return useCount;
}
@Override
public boolean use() {
if (canUse()) {
useCount ++;
if (parent != null) {
// TODO: Might consider a hierarchy (parent could overrule the result to false).
parent.use();
}
return true;
}
else {
// DenyUseCound is handled in sub-classes, if needed.
return false;
}
}
}

View File

@ -0,0 +1,153 @@
package fr.neatmonster.nocheatplus.workaround;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Simple registry for workarounds. No thread-safety built in.
*
* @author asofold
*
*/
public class SimpleWorkaroundRegistry implements WorkaroundRegistry {
/** Global counter by id. */
private final Map<String, WorkaroundCounter> counters = new HashMap<String, WorkaroundCounter>();
/** Workaround blue print by id. */
private final Map<String, Workaround> bluePrints = new HashMap<String, Workaround>();
/** Map group id to array of workaround ids. */
private final Map<String, String[]> groups = new HashMap<String, String[]>();
/** Map WorkaroundSet id to the contained blueprint ids. */
private final Map<String, String[]> workaroundSets = new HashMap<String, String[]>();
/** Map WorkaroundSet id to the contained group ids. Might not contain entries for all ids. */
private final Map<String, String[]> workaroundSetGroups = new HashMap<String, String[]>();
@Override
public void setWorkaroundBluePrint(final Workaround... bluePrints) {
for (int i = 0; i < bluePrints.length; i++) {
final Workaround workaround = bluePrints[i];
this.bluePrints.put(workaround.getId(), workaround.getNewInstance());
}
}
@Override
public void setGroup(final String groupId, final Collection<String> workaroundIds) {
groups.put(groupId, workaroundIds.toArray(new String[workaroundIds.size()]));
}
@Override
public void setWorkaroundSet(final String workaroundSetId, final Collection<Workaround> bluePrints, final String... groupIds) {
final String[] ids = new String[bluePrints.size()];
int i = 0;
for (final Workaround bluePrint : bluePrints) {
final String id = bluePrint.getId();
if (!this.bluePrints.containsKey(id)) {
// Lazily register.
setWorkaroundBluePrint(bluePrint);
}
ids[i] = id;
i ++;
}
this.workaroundSets.put(workaroundSetId, ids);
if (groupIds != null && groupIds.length > 0) {
for (i = 0; i < groupIds.length; i++) {
if (!this.groups.containsKey(groupIds[i])) {
throw new IllegalArgumentException("Group not registered: " + groupIds[i]);
}
}
this.workaroundSetGroups.put(workaroundSetId, groupIds);
}
}
@Override
public void setWorkaroundSetByIds(final String workaroundSetId, final Collection<String> bluePrintIds, final String... groupIds) {
final List<Workaround> bluePrints = new ArrayList<Workaround>(bluePrintIds.size());
for (final String id : bluePrintIds) {
final Workaround bluePrint = this.bluePrints.get(id);
if (bluePrint == null) {
throw new IllegalArgumentException("the blueprint is not registered: " + id);
}
bluePrints.add(bluePrint);
}
setWorkaroundSet(workaroundSetId, bluePrints, groupIds);
}
@Override
public WorkaroundSet getWorkaroundSet(final String workaroundSetId) {
final String[] workaroundIds = workaroundSets.get(workaroundSetId);
if (workaroundIds == null) {
throw new IllegalArgumentException("WorkaroundSet not registered: " + workaroundSetId);
}
final Workaround[] bluePrints = new Workaround[workaroundIds.length];
for (int i = 0; i < workaroundIds.length; i++) {
bluePrints[i] = this.bluePrints.get(workaroundIds[i]).getNewInstance();
}
final Map<String, String[]> groups;
final String[] groupIds = this.workaroundSetGroups.get(workaroundSetId);
if (groupIds == null) {
groups = null;
}
else {
groups = new HashMap<String, String[]>();
for (int i = 0; i < groupIds.length; i++) {
final String groupId = groupIds[i];
groups.put(groupId, this.groups.get(groupId));
}
}
return new WorkaroundSet(bluePrints, groups);
}
@Override
public WorkaroundCounter getGlobalCounter(final String id) {
return counters.get(id);
}
@Override
public WorkaroundCounter createGlobalCounter(final String id) {
WorkaroundCounter counter = counters.get(id);
if (counter == null) {
counter = new WorkaroundCounter(id);
counters.put(id, counter);
}
return counter;
}
@SuppressWarnings("unchecked")
@Override
public <C extends Workaround> C getWorkaround(final String id, final Class<C> workaroundClass) {
final Workaround workaround = getWorkaround(id);
if (workaroundClass.isAssignableFrom(workaround.getClass())) {
return (C) workaround;
}
else {
throw new IllegalArgumentException("Unsupported class for id '" + id + "': " + workaroundClass.getName() + " (actual class is " + workaround.getClass().getName() + ")");
}
}
@Override
public Workaround getWorkaround(final String id) {
final Workaround bluePrint = bluePrints.get(id);
if (bluePrint == null) {
throw new IllegalArgumentException("Id not registered as blueprint: " + id);
}
return bluePrint.getNewInstance();
}
@Override
public Map<String, Integer> getGlobalUseCount() {
final Map<String, Integer> currentCounts = new LinkedHashMap<String, Integer>(counters.size());
for (final WorkaroundCounter counter : counters.values()) {
currentCounts.put(counter.getId(), counter.getUseCount());
}
return currentCounts;
}
}

View File

@ -0,0 +1,60 @@
package fr.neatmonster.nocheatplus.workaround;
/**
* Provide a means of controlling when workarounds should be able to apply,
* enabling preconditions, mid-to-long-term side-conditions, as well as
* statistics for how often a workaround has been used.
* <hr>
* The method use() must be called after all other preconditions have been met
* for that (stage of) workaround, so that success means that the (stage of)
* workaround does apply. The method canUse can be used to test if the
* workaround would apply, aimed at cases where that is better performance-wise.
*
* @author asofold
*
*/
public interface Workaround {
// TODO: getDiscardCount() ?
// TODO: Add setEnabled() ? Allow to configure workarounds.
public String getId();
/** The all-time use count. */
public int getUseCount();
/**
* Attempt to use the workaround, considering all preconditions and
* side-conditions set. This will increase the use count in case of
* returning true, it might also alter/use other counters based on the value
* to be returned.
*
* @return If actually can be used.
*/
public boolean use();
/**
* Test if this (stage of) workaround would apply, excluding checking for
* parent state. This must not have any side effect nor change any data.
* This should also not call parent.canUse, as that would lead to quadratic
* time checking with a deeper hierarchy (likely it's just 1 deep).
*
* @return
*/
public boolean canUse();
/**
* Generic reset to the initial conditions. This does not reset the use
* count, other effects depend on the implementation.
*/
public void resetConditions();
/**
* Serving as factory, retrieve a new instance of the same kind, in the
* default state (not clone).
*
* @return
*/
public Workaround getNewInstance();
}

View File

@ -0,0 +1,52 @@
package fr.neatmonster.nocheatplus.workaround;
/**
* Count down to 0, use is only possible if the currentCounter is greater than
* 0. An initialCounter of 0 together with setting the currentCount manually,
* allows to activate a workaround on base of a precondition.
*
* @author asofold
*
*/
public class WorkaroundCountDown extends AbstractWorkaround {
private final int initialCount;
private int currentCount;
public WorkaroundCountDown(String id, int initialCount, Workaround parent) {
super(id, parent);
this.initialCount = initialCount;
this.currentCount = initialCount;
}
public void setCurrentCount(int currentCount) {
this.currentCount = currentCount;
}
@Override
public boolean use() {
// Adjust currentCount based on super.use().
if (super.use()) {
currentCount --;
return true;
} else {
return false;
}
}
@Override
public boolean canUse() {
return currentCount > 0;
}
@Override
public void resetConditions() {
currentCount = initialCount;
}
@Override
public WorkaroundCountDown getNewInstance() {
return new WorkaroundCountDown(getId(), initialCount, getParent());
}
}

View File

@ -0,0 +1,34 @@
package fr.neatmonster.nocheatplus.workaround;
/**
* Simply count times used.
* @author asofold
*
*/
public class WorkaroundCounter extends AbstractWorkaround {
public WorkaroundCounter(String id, Workaround parent) {
super(id, parent);
}
public WorkaroundCounter(String id) {
super(id);
}
@Override
public boolean canUse() {
// Just counting.
return true;
}
@Override
public void resetConditions() {
// Nothing to do.
}
@Override
public WorkaroundCounter getNewInstance() {
return new WorkaroundCounter(getId());
}
}

View File

@ -0,0 +1,225 @@
package fr.neatmonster.nocheatplus.workaround;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* An access point for fetching global WorkaroundCounter instances and a factory
* for fetching new sets of per-player workarounds.
*
* @author asofold
*
*/
public interface WorkaroundRegistry {
/**
* Convenience to retrieve any type of per-player Workaround by id, for the
* case one doesn't want to store the registry and/or individual Workaround
* implementations as members. Groups allow resetting certain types of
* workarounds in bunches.
*
* @author asofold
*
*/
public static class WorkaroundSet {
// TODO: getUseCount()
// TODO: A list for ids of just used workarounds (reset externally. Add use(id) vs alter Workaround)?
// TODO: Better optimized constructor.
/** Map workaround id to workaround. */
private final Map<String, Workaround> workaroundsById = new LinkedHashMap<String, Workaround>();
/** Only the workarounds that might need resetting. */
private final Workaround[] mightNeedReset;
/** Map groupId to workarounds. */
private final Map<String, Workaround[]> groups;
/**
*
* @param bluePrints
* @param groups
* Map groupId to workaroundIds, groups may be null if none
* are set. All referenced workaround ids must be registered,
* workarounds can be in multiple groups.
*/
public WorkaroundSet(final Workaround[] bluePrints, final Map<String, String[]> groups) {
final Class<?> excludeFromReset = WorkaroundCounter.class;
final List<Workaround> mightNeedReset = new ArrayList<Workaround>(bluePrints.length);
for (int i = 0; i < bluePrints.length; i++) {
final Workaround workaround = bluePrints[i].getNewInstance();
workaroundsById.put(workaround.getId(), workaround);
if (workaround.getClass() != excludeFromReset) {
mightNeedReset.add(workaround);
}
}
this.mightNeedReset = mightNeedReset.toArray(new Workaround[mightNeedReset.size()]);
// Prepare fast to reset lists, if groups are given.
if (groups != null) {
this.groups = new HashMap<String, Workaround[]>();
for (final Entry<String, String[]> entry : groups.entrySet()) {
final String[] workaroundIds = entry.getValue();
final Workaround[] group = new Workaround[workaroundIds.length];
for (int i = 0; i < workaroundIds.length; i++) {
group[i] = getWorkaround(workaroundIds[i]);
}
this.groups.put(entry.getKey(), group);
}
} else {
this.groups = null;
}
}
@SuppressWarnings("unchecked")
public <C extends Workaround> C getWorkaround(final String id, final Class<C> workaroundClass) {
final Workaround present = getWorkaround(id);
if (!workaroundClass.isAssignableFrom(present.getClass())) {
throw new IllegalArgumentException("Wrong type of registered workaround requested: " + workaroundClass.getName() + " instead of " + present.getClass().getName());
} else {
return (C) present;
}
}
public Workaround getWorkaround(final String id) {
final Workaround present = workaroundsById.get(id);
if (present == null) {
throw new IllegalArgumentException("Workaround id not registered: " + id);
}
return present;
}
/**
* Call resetConditions for all stored workarounds, excluding
* WorkaroundCounter instances (sub classes get reset too).
*/
public void resetConditions() {
for (int i = 0; i < mightNeedReset.length; i++) {
mightNeedReset[i].resetConditions();
}
}
/**
* Call resetConditions for all workarounds that are within the group
* with the given groupId.
*
* @param groupId
*/
public void resetConditions(final String groupId) {
final Workaround[] workarounds = groups.get(groupId);
if (workarounds == null) {
throw new IllegalArgumentException("Group not registered: " + groupId);
}
for (int i = 0; i < workarounds.length; i++) {
workarounds[i].resetConditions();
}
}
}
// TODO: Might make getWorkaround non public, to favor use of WorkaroundSet.
/**
* Registers workaround.getNewInstance() for the set id. Set parent to
* createGlobalCounter(id), if a global counter is desired.
*
* @param bluePrints
*/
public void setWorkaroundBluePrint(Workaround...bluePrints);
/**
* Specify what workaround ids belong to a certain group. Workarounds can be
* in multiple groups.
*
* @param groupId
* @param workaroundIds
*/
public void setGroup(String groupId, Collection<String> workaroundIds);
/**
* Define which workarounds and which groups belong to the WorkaroundSet of
* the given workaroundSetId.
*
* @param workaroundSetId
* @param bluePrints
* Lazily registers, if no blueprint is present. Already
* registered blueprints are kept.
* @param groupIds
* Must already be registered.
*/
public void setWorkaroundSet(String workaroundSetId, Collection<Workaround> bluePrints, String... groupIds);
/**
* Define which workarounds and which groups belong to the WorkaroundSet of
* the given workaroundSetId.
*
* @param workaroundSetId
* @param bluePrintIds
* @param groupIds
*/
public void setWorkaroundSetByIds(String workaroundSetId, Collection<String> bluePrintIds, String... groupIds);
/**
* Retrieve a pre-set WorkaroundSet instance with new Workaround instances
* generated from the blueprints.
*
* @param workaroundSetId
* @return
*/
public WorkaroundSet getWorkaroundSet(String workaroundSetId);
/**
* Get a registered global WorkaroundCounter, if registered.
*
* @param id
* @return The registered WorkaroundCounter instance, or null if none is
* registered for the given id.
*/
public WorkaroundCounter getGlobalCounter(String id);
/**
* Get a registered global WorkaroundCounter, create if not present.
*
* @param id
* @return
*/
public WorkaroundCounter createGlobalCounter(String id);
/**
* Retrieve a new instance, ready for use, attached to a global counter of
* the same id.
*
* @param id
* @param workaroundClass
* Specific type to use. The registry may have a blueprint set
* and just clone that.
* @return
* @throws IllegalArgumentException
* If either of id or workaroundClass is not possible to use.
*/
public <C extends Workaround> C getWorkaround(String id, Class<C> workaroundClass);
/**
* Retrieve a new instance, ready for use, attached to a global counter of
* the same id.
*
* @param id
* @return
* @throws IllegalArgumentException
* If either of id or workaroundClass is not possible to use.
*/
public Workaround getWorkaround(String id);
/**
* Get all global count values by id.
*
* @return
*/
public Map<String, Integer> getGlobalUseCount();
}

View File

@ -696,6 +696,7 @@ public class SurvivalFly extends Check {
// Hack for allow sprint-jumping with slowness.
if (sprinting && hAllowedDistance < 0.29 && cc.sfSlownessSprintHack && player.hasPotionEffect(PotionEffectType.SLOW)) {
// TODO: Should restrict further by yDistance, ground and other (jumping only).
// TODO: Restrict to not in water (depth strider)?
hAllowedDistance = slownessSprintHack(player, hAllowedDistance);
}
}

View File

@ -658,6 +658,8 @@ public abstract class ConfPaths {
public static final String COMPATIBILITY_EXEMPTIONS_REMOVE = COMPATIBILITY_EXEMPTIONS + "remove.";
public static final String COMPATIBILITY_EXEMPTIONS_REMOVE_JOIN = COMPATIBILITY_EXEMPTIONS_REMOVE + "join";
public static final String COMPATIBILITY_EXEMPTIONS_REMOVE_LEAVE = COMPATIBILITY_EXEMPTIONS_REMOVE + "leave";
// TODO: remove: tick, metadata.
// TODO: npcs: active, checks (default: fight, moving, all the noswings, wrongblock etc.)
public static final String COMPATIBILITY_SERVER = COMPATIBILITY + "server.";
public static final String COMPATIBILITY_SERVER_CBDEDICATED = COMPATIBILITY_SERVER + "cbdedicated.";

View File

@ -7,6 +7,7 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.bukkit.entity.NPC;
import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.checks.CheckType;
@ -119,9 +120,21 @@ public class NCPExemptionManager {
*/
public static final boolean isExempted(final Player player, final CheckType checkType) {
// TODO: Settings: If to check meta data at all.
// TODO: Settings: check types to exempt npcs from (and if to use) -> implement setSettings
return isExempted(player.getUniqueId(), checkType) || player.hasMetadata("nocheat.exempt");
}
/**
* Check if a player is an npc, using current settings.
*
* @param player
* @return
*/
public static final boolean isNpc(final Player player) {
// TODO: Configurability: Which metadata key(s + if).
return (player instanceof NPC) || player.hasMetadata("npc");
}
/**
* Undo exempting an entity from all checks. Includes players, note that
* exemption by meta data is not removed here.