diff --git a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/AbstractWorkaround.java b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/AbstractWorkaround.java new file mode 100644 index 00000000..05620159 --- /dev/null +++ b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/AbstractWorkaround.java @@ -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; + } + } + +} diff --git a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/SimpleWorkaroundRegistry.java b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/SimpleWorkaroundRegistry.java new file mode 100644 index 00000000..91703128 --- /dev/null +++ b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/SimpleWorkaroundRegistry.java @@ -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 counters = new HashMap(); + + /** Workaround blue print by id. */ + private final Map bluePrints = new HashMap(); + + /** Map group id to array of workaround ids. */ + private final Map groups = new HashMap(); + + /** Map WorkaroundSet id to the contained blueprint ids. */ + private final Map workaroundSets = new HashMap(); + + /** Map WorkaroundSet id to the contained group ids. Might not contain entries for all ids. */ + private final Map workaroundSetGroups = new HashMap(); + + @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 workaroundIds) { + groups.put(groupId, workaroundIds.toArray(new String[workaroundIds.size()])); + } + + @Override + public void setWorkaroundSet(final String workaroundSetId, final Collection 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 bluePrintIds, final String... groupIds) { + final List bluePrints = new ArrayList(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 groups; + final String[] groupIds = this.workaroundSetGroups.get(workaroundSetId); + if (groupIds == null) { + groups = null; + } + else { + groups = new HashMap(); + 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 getWorkaround(final String id, final Class 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 getGlobalUseCount() { + final Map currentCounts = new LinkedHashMap(counters.size()); + for (final WorkaroundCounter counter : counters.values()) { + currentCounts.put(counter.getId(), counter.getUseCount()); + } + return currentCounts; + } + +} diff --git a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/Workaround.java b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/Workaround.java new file mode 100644 index 00000000..d8d6a4b3 --- /dev/null +++ b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/Workaround.java @@ -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. + *
+ * 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(); + +} diff --git a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/WorkaroundCountDown.java b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/WorkaroundCountDown.java new file mode 100644 index 00000000..0513f731 --- /dev/null +++ b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/WorkaroundCountDown.java @@ -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()); + } + +} diff --git a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/WorkaroundCounter.java b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/WorkaroundCounter.java new file mode 100644 index 00000000..f6a13ff2 --- /dev/null +++ b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/WorkaroundCounter.java @@ -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()); + } + +} diff --git a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/WorkaroundRegistry.java b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/WorkaroundRegistry.java new file mode 100644 index 00000000..a5c77fef --- /dev/null +++ b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/workaround/WorkaroundRegistry.java @@ -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 workaroundsById = new LinkedHashMap(); + + /** Only the workarounds that might need resetting. */ + private final Workaround[] mightNeedReset; + + /** Map groupId to workarounds. */ + private final Map 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 groups) { + final Class excludeFromReset = WorkaroundCounter.class; + final List mightNeedReset = new ArrayList(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(); + for (final Entry 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 getWorkaround(final String id, final Class 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 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 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 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 getWorkaround(String id, Class 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 getGlobalUseCount(); + +} diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/SurvivalFly.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/SurvivalFly.java index 6ba537b0..6fdc1602 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/SurvivalFly.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/moving/SurvivalFly.java @@ -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); } } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java index d6daaf1c..681a35d8 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java @@ -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."; diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/hooks/NCPExemptionManager.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/hooks/NCPExemptionManager.java index 21237164..562224c1 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/hooks/NCPExemptionManager.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/hooks/NCPExemptionManager.java @@ -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.