Add the "ncp top" command, allowing to search all the violation history.

Original pull request:
https://github.com/NoCheatPlus/NoCheatPlus/pull/24

This probably is not the final implementation, but it allows some
minimal freedom:
* Specify number of entries to show.
* Specify check types (and groups!).
* Specify what to sort by.

There might be need for some merged view, combining several different
check types somehow, or just shortcuts for specific selections, e.g. for
fighting-related checks.

----

+ Fix root command not showing sub commmand usage.
This commit is contained in:
asofold 2014-07-29 12:40:54 +02:00
parent 9b6c717fc0
commit c2722abc19
6 changed files with 557 additions and 161 deletions

View File

@ -0,0 +1,37 @@
package fr.neatmonster.nocheatplus.utilities;
import java.util.Collection;
import java.util.Comparator;
/**
* Allow to sort by multiple criteria, first come first serve.
* @author dev1mc
*
*/
public class FCFSComparator <T> implements Comparator<T> {
private final Comparator<T>[] comparators;
private final boolean reverse;
public FCFSComparator(Collection<Comparator<T>> comparators) {
this(comparators, false);
}
@SuppressWarnings("unchecked")
public FCFSComparator(Collection<Comparator<T>> comparators, boolean reverse) {
this.comparators = (Comparator<T>[]) comparators.toArray();
this.reverse = reverse;
}
@Override
public int compare(T o1, T o2) {
for (int i = 0; i < comparators.length; i++) {
final int res = comparators[i].compare(o1, o2);
if (res != 0) {
return reverse ? -res : res;
}
}
return 0;
}
}

View File

@ -5,12 +5,17 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.hooks.APIUtils;
import fr.neatmonster.nocheatplus.utilities.FCFSComparator;
/**
* The class containg the violation history of a player.
@ -22,30 +27,29 @@ public class ViolationHistory {
* (Comparable by time.)
*/
public static class ViolationLevel{
/**
* Descending sort.
*/
public static Comparator<ViolationLevel> VLComparator = new Comparator<ViolationHistory.ViolationLevel>() {
@Override
public int compare(final ViolationLevel vl1, final ViolationLevel vl2) {
if (vl1.time == vl2.time) return 0;
else if (vl1.time < vl2.time) return 1;
else return -1;
}
};
/**
* Descending sort by time.
*/
public static Comparator<ViolationLevel> VLComparator = new Comparator<ViolationHistory.ViolationLevel>() {
@Override
public int compare(final ViolationLevel vl1, final ViolationLevel vl2) {
return Long.compare(vl1.time, vl2.time);
}
};
/** The check. */
public final String check;
/** The sum of violation levels added. */
public double sumVL;
/** Number of violations. */
public int nVL;
/** Maximal violation level added. */
public double maxVL;
/** The last VL time. */
public long time;
@ -79,20 +83,128 @@ public class ViolationHistory {
time = System.currentTimeMillis();
}
@Override
public boolean equals(final Object obj) {
// Might add String.
if (obj instanceof ViolationLevel)
return this.check.equals(((ViolationLevel) obj).check);
else return false;
}
@Override
public boolean equals(final Object obj) {
// Might add String.
if (obj instanceof ViolationLevel)
return this.check.equals(((ViolationLevel) obj).check);
else return false;
}
@Override
public int hashCode() {
return check.hashCode();
}
@Override
public int hashCode() {
return check.hashCode();
}
}
public static class VLView {
public static final Comparator<VLView> CmpName = new Comparator<ViolationHistory.VLView>() {
@Override
public int compare(VLView o1, VLView o2) {
return o1.name.compareToIgnoreCase(o2.name);
}
};
public static final Comparator<VLView> CmpCheck = new Comparator<ViolationHistory.VLView>() {
@Override
public int compare(VLView o1, VLView o2) {
return o1.check.compareToIgnoreCase(o2.check);
}
};
public static final Comparator<VLView> CmpSumVL = new Comparator<ViolationHistory.VLView>() {
@Override
public int compare(VLView o1, VLView o2) {
return Double.compare(o1.sumVL, o2.sumVL);
}
};
public static final Comparator<VLView> CmpnVL = new Comparator<ViolationHistory.VLView>() {
@Override
public int compare(VLView o1, VLView o2) {
return Integer.compare(o1.nVL, o2.nVL);
}
};
public static final Comparator<VLView> CmpAvgVL = new Comparator<ViolationHistory.VLView>() {
@Override
public int compare(VLView o1, VLView o2) {
return Double.compare(o1.sumVL / o1.nVL, o2.sumVL / o2.nVL);
}
};
public static final Comparator<VLView> CmpMaxVL = new Comparator<ViolationHistory.VLView>() {
@Override
public int compare(VLView o1, VLView o2) {
return Double.compare(o1.maxVL, o2.maxVL);
}
};
public static final Comparator<VLView> CmpTime = new Comparator<ViolationHistory.VLView>() {
@Override
public int compare(VLView o1, VLView o2) {
return Long.compare(o1.time, o2.time);
}
};
/**
* Get a mixed/fcfs comparator from parsing given args. Accepted are
* @param args
* @param startIndex
* @return If none are found, null is returned, no errors will be thrown, duplicates are removed.
*/
public static Comparator<VLView> parseMixedComparator(String[] args, int startIndex) {
final Set<Comparator<VLView>> comparators = new LinkedHashSet<Comparator<VLView>>();
for (int i = startIndex; i < args.length; i ++) {
String arg = args[i].toLowerCase();
while (arg.startsWith("-")) {
arg = arg.substring(1);
}
if (arg.matches("(name|player|playername)")) {
comparators.add(CmpName);
} else if (arg.matches("(check|type|checktype)")) {
comparators.add(CmpCheck);
} else if (arg.matches("(sum|sumvl|vl)")) {
comparators.add(CmpSumVL);
} else if (arg.matches("(n|num|number|nvl)")) {
comparators.add(CmpnVL);
} else if (arg.matches("(avg|av|average|averagevl|avgvl|avvl|avl)")) {
comparators.add(CmpAvgVL);
} else if (arg.matches("(max|maxvl|maximum|maximumvl)")) {
comparators.add(CmpMaxVL);
} else if (arg.matches("(time|t)")) {
comparators.add(CmpTime);
}
}
if (comparators.isEmpty()) {
return null;
}
return new FCFSComparator<ViolationHistory.VLView>(comparators, true);
}
public final String name;
public final String check;
public final double sumVL;
public final int nVL;
public final double maxVL;
public final long time;
public VLView(String name, ViolationLevel vl) {
this(name, vl.check, vl.sumVL, vl.nVL, vl.maxVL, vl.time);
}
public VLView(String name, String check, double sumVL, int nVL, double maxVL, long time) {
this.name = name;
this.check = check;
this.sumVL = sumVL;
this.nVL = nVL;
this.maxVL = maxVL;
this.time = time;
}
}
/** Map the check string names to check types (workaround, keep at default, set by Check)*/
static Map<String, CheckType> checkTypeMap = new HashMap<String, CheckType>();
@ -110,7 +222,7 @@ public class ViolationHistory {
public static ViolationHistory getHistory(final Player player) {
return getHistory(player.getName(), true);
}
/**
* Get the history of a player, create if desired and not present.
* @param player
@ -122,7 +234,7 @@ public class ViolationHistory {
public static ViolationHistory getHistory(final Player player, final boolean create) {
return getHistory(player.getName(), create);
}
/**
* Gets the history of a player by exact name.
* @param playerName
@ -132,43 +244,75 @@ public class ViolationHistory {
* @return
*/
public static ViolationHistory getHistory(final String playerName, final boolean create) {
final ViolationHistory hist = violationHistories.get(playerName);
if (hist != null)
return hist;
else if (create){
final ViolationHistory newHist = new ViolationHistory();
violationHistories.put(playerName, newHist);
return newHist;
}
else
return null;
final ViolationHistory hist = violationHistories.get(playerName);
if (hist != null)
return hist;
else if (create){
final ViolationHistory newHist = new ViolationHistory();
violationHistories.put(playerName, newHist);
return newHist;
}
else
return null;
}
/**
* Get a list of VLView instances for direct check type matches (no inheritance checks).
* @param checkType
* @return Always returns a list.
*/
public static List<VLView> getView(final CheckType checkType) {
final List<VLView> view = new LinkedList<VLView>();
for (final Entry<String, ViolationHistory> entry: violationHistories.entrySet()) {
final ViolationHistory hist = entry.getValue();
final ViolationLevel vl = hist.getViolationLevel(checkType);
if (vl != null) {
view.add(new VLView(entry.getKey(), vl));
}
}
return view;
}
public static ViolationHistory removeHistory(final String playerName){
return violationHistories.remove(playerName);
return violationHistories.remove(playerName);
}
public static void clear(final CheckType checkType){
for (ViolationHistory hist : violationHistories.values()){
hist.remove(checkType);
}
for (ViolationHistory hist : violationHistories.values()){
hist.remove(checkType);
}
}
/** The violation levels for every check. */
private final List<ViolationLevel> violationLevels = new ArrayList<ViolationLevel>();
/**
* Gets the violation levels.
* Gets the violation levels. Sorted by time, descending.
*
* @return the violation levels
*/
public ViolationLevel[] getViolationLevels() {
final ViolationLevel[] sortedLevels = new ViolationLevel[violationLevels.size()];
violationLevels.toArray(sortedLevels);
Arrays.sort(sortedLevels, ViolationLevel.VLComparator); // Descending sort.;
final ViolationLevel[] sortedLevels = new ViolationLevel[violationLevels.size()];
violationLevels.toArray(sortedLevels);
Arrays.sort(sortedLevels, ViolationLevel.VLComparator); // Descending sort.;
return sortedLevels;
}
/**
* Return only direct matches, no inheritance checking.
* @param type
* @return ViolationLevel instance, if present. Otherwise null.
*/
public ViolationLevel getViolationLevel(final CheckType type) {
for (int i = 0; i < violationLevels.size(); i++) {
final ViolationLevel vl = violationLevels.get(i);
if (checkTypeMap.get(vl.check) == type) {
return vl;
}
}
return null;
}
/**
* Log a VL.
*
@ -191,23 +335,23 @@ public class ViolationHistory {
* @param checkType
* @return If entries were removed.
*/
public boolean remove(final CheckType checkType) {
if (checkType == CheckType.ALL){
final boolean empty = violationLevels.isEmpty();
violationLevels.clear();
return !empty;
}
final Iterator<ViolationLevel> it = violationLevels.iterator();
boolean found = false;
while (it.hasNext()){
final ViolationLevel vl = it.next();
final CheckType refType = checkTypeMap.get(vl.check);
if (refType == null) continue;
if (refType == checkType || APIUtils.isParent(checkType, refType)){
found = true;
it.remove();
}
}
return found;
}
public boolean remove(final CheckType checkType) {
if (checkType == CheckType.ALL){
final boolean empty = violationLevels.isEmpty();
violationLevels.clear();
return !empty;
}
final Iterator<ViolationLevel> it = violationLevels.iterator();
boolean found = false;
while (it.hasNext()){
final ViolationLevel vl = it.next();
final CheckType refType = checkTypeMap.get(vl.check);
if (refType == null) continue;
if (refType == checkType || APIUtils.isParent(checkType, refType)){
found = true;
it.remove();
}
}
return found;
}
}

View File

@ -31,6 +31,7 @@ import fr.neatmonster.nocheatplus.command.admin.exemption.UnexemptCommand;
import fr.neatmonster.nocheatplus.command.admin.log.LogCommand;
import fr.neatmonster.nocheatplus.command.admin.notify.NotifyCommand;
import fr.neatmonster.nocheatplus.command.admin.reset.ResetCommand;
import fr.neatmonster.nocheatplus.command.admin.top.TopCommand;
import fr.neatmonster.nocheatplus.components.INotifyReload;
import fr.neatmonster.nocheatplus.config.ConfPaths;
import fr.neatmonster.nocheatplus.config.ConfigFile;
@ -69,7 +70,7 @@ public class NoCheatPlusCommand extends BaseCommand{
}
}
private Set<String> rootLabels = new LinkedHashSet<String>();
private Set<String> rootLabels = new LinkedHashSet<String>();
/**
* Instantiates a new command handler.
@ -78,45 +79,46 @@ public class NoCheatPlusCommand extends BaseCommand{
* the instance of NoCheatPlus
*/
public NoCheatPlusCommand(final JavaPlugin plugin, final List<INotifyReload> notifyReload) {
super(plugin, "nocheatplus", null, new String[]{"ncp"});
super(plugin, "nocheatplus", null, new String[]{"ncp"});
// Register sub commands (special order):
for (BaseCommand cmd : new BaseCommand[]{
new BanCommand(plugin),
new CommandsCommand(plugin),
new DelayCommand(plugin),
new ExemptCommand(plugin),
new ExemptionsCommand(plugin),
new InfoCommand(plugin),
new InspectCommand(plugin),
new KickCommand(plugin),
new KickListCommand(plugin),
new LagCommand(plugin),
new VersionCommand(plugin),
new NotifyCommand(plugin),
new ReloadCommand(plugin, notifyReload),
new RemovePlayerCommand(plugin),
new TellCommand(plugin),
new DenyLoginCommand(plugin),
new UnexemptCommand(plugin),
new AllowLoginCommand(plugin),
new LogCommand(plugin),
new ResetCommand(plugin),
new BanCommand(plugin),
new CommandsCommand(plugin),
new DelayCommand(plugin),
new ExemptCommand(plugin),
new ExemptionsCommand(plugin),
new TopCommand(plugin),
new InfoCommand(plugin),
new InspectCommand(plugin),
new KickCommand(plugin),
new KickListCommand(plugin),
new LagCommand(plugin),
new VersionCommand(plugin),
new NotifyCommand(plugin),
new ReloadCommand(plugin, notifyReload),
new RemovePlayerCommand(plugin),
new TellCommand(plugin),
new DenyLoginCommand(plugin),
new UnexemptCommand(plugin),
new AllowLoginCommand(plugin),
new LogCommand(plugin),
new ResetCommand(plugin),
}){
addSubCommands(cmd);
rootLabels.add(cmd.label);
addSubCommands(cmd);
rootLabels.add(cmd.label);
}
}
/**
* Retrieve a collection with all sub-command permissions.
* @return
*/
public Collection<String> getAllSubCommandPermissions(){
final Set<String> set = new LinkedHashSet<String>(rootLabels.size());
for (final String label : rootLabels){
set.add(subCommands.get(label).permission);
}
return set;
final Set<String> set = new LinkedHashSet<String>(rootLabels.size());
for (final String label : rootLabels){
set.add(subCommands.get(label).permission);
}
return set;
}
/* (non-Javadoc)
@ -126,82 +128,88 @@ public class NoCheatPlusCommand extends BaseCommand{
@Override
public boolean onCommand(final CommandSender sender, final Command command, final String commandLabel,
final String[] args) {
if (!command.getName().equalsIgnoreCase("nocheatplus")){
// Not our command, how did it get here?
return false;
// Not our command, how did it get here?
return false;
}
if (sender.hasPermission(Permissions.FILTER_COMMAND_NOCHEATPLUS)){
// Check sub-commands.
// Check sub-commands.
if (args.length > 0){
AbstractCommand<?> subCommand = subCommands.get(args[0].trim().toLowerCase());
if (subCommand != null && subCommand.testPermission(sender, command, commandLabel, args)){
// Sender has permission to run the command.
return subCommand.onCommand(sender, command, commandLabel, args);
}
AbstractCommand<?> subCommand = subCommands.get(args[0].trim().toLowerCase());
if (subCommand != null && subCommand.testPermission(sender, command, commandLabel, args)){
// Sender has permission to run the command.
final boolean res = subCommand.onCommand(sender, command, commandLabel, args);
if (!res && subCommand.usage != null) {
sender.sendMessage(subCommand.usage);
return true;
} else {
return res;
}
}
}
// No sub command worked, print usage.
return false;
// No sub command worked, print usage.
return false;
}
final ConfigFile config = ConfigManager.getConfigFile();
if (config.getBoolean(ConfPaths.PROTECT_PLUGINS_HIDE_ACTIVE)){
// Prevent the NCP usage printout:
// TODO: GetColoredString
sender.sendMessage(ColorUtil.replaceColors(config.getString(ConfPaths.PROTECT_PLUGINS_HIDE_NOCOMMAND_MSG)));
return true;
// Prevent the NCP usage printout:
// TODO: GetColoredString
sender.sendMessage(ColorUtil.replaceColors(config.getString(ConfPaths.PROTECT_PLUGINS_HIDE_NOCOMMAND_MSG)));
return true;
}
else{
return false;
return false;
}
}
// /**
// * Check which of the choices starts with prefix
// * @param sender
// * @param choices
// * @return
// */
// protected List<String> getTabMatches(CommandSender sender, Collection<String> choices, String prefix){
// final List<String> res = new ArrayList<String>(choices.size());
// final Set<BaseCommand> done = new HashSet<BaseCommand>();
// for (final String label : choices){
// if (!label.startsWith(prefix)) continue;
// final BaseCommand cmd = commands.get(label);
// if (done.contains(cmd)) continue;
// done.add(cmd);
// if (sender.hasPermission(cmd.permission)) res.add(cmd.label);
// }
// if (!res.isEmpty()){
// Collections.sort(res);
// return res;
// }
// return null;
// }
// /**
// * Check which of the choices starts with prefix
// * @param sender
// * @param choices
// * @return
// */
// protected List<String> getTabMatches(CommandSender sender, Collection<String> choices, String prefix){
// final List<String> res = new ArrayList<String>(choices.size());
// final Set<BaseCommand> done = new HashSet<BaseCommand>();
// for (final String label : choices){
// if (!label.startsWith(prefix)) continue;
// final BaseCommand cmd = commands.get(label);
// if (done.contains(cmd)) continue;
// done.add(cmd);
// if (sender.hasPermission(cmd.permission)) res.add(cmd.label);
// }
// if (!res.isEmpty()){
// Collections.sort(res);
// return res;
// }
// return null;
// }
// @Override
// public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args)
// {
// // TODO: TabComplete check ?
// if (args.length == 0 || args.length == 1 && args[0].trim().isEmpty()){
// // Add labels without aliases.
// return getTabMatches(sender, rootLabels, "");
// }
// else {
// final String subLabel = args[0].trim().toLowerCase();
// if (args.length == 1){
// // Also check aliases for matches.
// return getTabMatches(sender, commands.keySet(), subLabel);
// }
// else{
// final NCPCommand cmd = commands.get(subLabel);
// if (cmd.testPermission...){
// // Delegate the tab-completion.
// return cmd.onTabComplete(sender, command, alias, args);
// }
// }
// }
// return null;
// }
// @Override
// public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args)
// {
// // TODO: TabComplete check ?
// if (args.length == 0 || args.length == 1 && args[0].trim().isEmpty()){
// // Add labels without aliases.
// return getTabMatches(sender, rootLabels, "");
// }
// else {
// final String subLabel = args[0].trim().toLowerCase();
// if (args.length == 1){
// // Also check aliases for matches.
// return getTabMatches(sender, commands.keySet(), subLabel);
// }
// else{
// final NCPCommand cmd = commands.get(subLabel);
// if (cmd.testPermission...){
// // Delegate the tab-completion.
// return cmd.onTabComplete(sender, command, alias, args);
// }
// }
// }
// return null;
// }
}

View File

@ -0,0 +1,202 @@
package fr.neatmonster.nocheatplus.command.admin.top;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.ViolationHistory;
import fr.neatmonster.nocheatplus.checks.ViolationHistory.VLView;
import fr.neatmonster.nocheatplus.command.BaseCommand;
import fr.neatmonster.nocheatplus.hooks.APIUtils;
import fr.neatmonster.nocheatplus.permissions.Permissions;
import fr.neatmonster.nocheatplus.utilities.FCFSComparator;
public class TopCommand extends BaseCommand{
protected static class PrimaryThreadWorker implements Runnable{
private final Collection<CheckType> checkTypes;
private final CommandSender sender;
private final Comparator<VLView> comparator;
private final int n;
private final Plugin plugin;
public PrimaryThreadWorker(CommandSender sender, Collection<CheckType> checkTypes, Comparator<VLView> comparator, int n, Plugin plugin) {
this.checkTypes = new LinkedHashSet<CheckType>(checkTypes);
this.sender = sender;
this.comparator = comparator;
this.n = n;
this.plugin = plugin;
}
@Override
public void run() {
final Iterator<CheckType> it = checkTypes.iterator();
List<VLView> views = null;
CheckType type = null;
while (it.hasNext()) {
type = it.next();
it.remove();
views = ViolationHistory.getView(type);
if (views.isEmpty()) {
views = null;
} else {
break;
}
}
if (views == null) {
sender.sendMessage("No more history to process.");
} else {
// Start sorting and result processing asynchronously.
Bukkit.getScheduler().runTaskAsynchronously(plugin,
new AsynchronousWorker(sender, type, views, checkTypes, comparator, n, plugin));
}
}
}
protected static class AsynchronousWorker implements Runnable{
private final CommandSender sender;
private final CheckType checkType;
private final List<VLView> views;
private final Collection<CheckType> checkTypes;
private final Comparator<VLView> comparator;
private final int n;
private final Plugin plugin;
public AsynchronousWorker(CommandSender sender, CheckType checkType, List<VLView> views, Collection<CheckType> checkTypes, Comparator<VLView> comparator, int n, Plugin plugin) {
this.sender = sender;
this.checkType = checkType;
this.views = views;
this.checkTypes = checkTypes;
this.comparator = comparator;
this.n = n;
this.plugin = plugin;
}
@Override
public void run() {
final DecimalFormat format = new DecimalFormat("#.#");
// Sort
Collections.sort(views, comparator);
// Display.
final StringBuilder builder = new StringBuilder(100 + 32 * views.size());
builder.append(checkType.toString());
builder.append(":");
final String c1, c2;
if (sender instanceof Player) {
c1 = ChatColor.WHITE.toString();
c2 = ChatColor.GRAY.toString();
} else {
c1 = c2 = "";
}
int done = 0;
for (final VLView view : views) {
builder.append(" " + c1);
builder.append(view.name);
// Details
builder.append(c2 + "(");
// sum
builder.append("sum=");
builder.append(format.format(view.sumVL));
// n
builder.append("/n=");
builder.append(view.nVL);
// avg
builder.append("/avg=");
builder.append(format.format(view.sumVL / view.nVL));
// max
builder.append("/max=");
builder.append(format.format(view.maxVL));
builder.append(")");
if (done >= n) {
break;
}
}
if (views.isEmpty()) {
builder.append(c1 + "Nothing to display.");
}
final String message = builder.toString();
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin,
new Runnable() {
@Override
public void run() {
sender.sendMessage(message);
}
});
if (!checkTypes.isEmpty()) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin,
new PrimaryThreadWorker(sender, checkTypes, comparator, n, plugin));
}
}
}
public TopCommand(JavaPlugin plugin) {
super(plugin, "top", Permissions.COMMAND_TOP);
this.usage = "Optional: Specify number of entries to show (once).\nObligatory: Specify check types (multiple possible).\nOptional: Specify what to sort by (multiple possible: -sumvl, -avgvl, -maxvl, -nvl, -name, -time).\nThis is a heavy operation, use with care."; // -check
}
@Override
public boolean onCommand(CommandSender sender, Command command, String alias, String[] args) {
if (args.length < 2) {
return false;
}
int startIndex = 1;
Integer n = 10;
try {
n = Integer.parseInt(args[1].trim());
startIndex = 2;
} catch (NumberFormatException e) {}
if (n <= 0) {
sender.sendMessage("Setting number of entries to 10");
n = 1;
} else if ((sender instanceof Player) && n > 300) {
sender.sendMessage("Capping number of entries at 300.");
n = 300;
} else if (n > 10000) {
sender.sendMessage("Capping number of entries at 10000.");
n = 10000;
}
Set<CheckType> checkTypes = new LinkedHashSet<CheckType>();
for (int i = startIndex; i < args.length; i ++) {
CheckType type = null;
try {
type = CheckType.valueOf(args[i].trim().toUpperCase().replace('-', '_').replace('.', '_'));
} catch (Throwable t) {} // ...
if (type != null) {
checkTypes.addAll(APIUtils.getChildren(type)); // Includes type.
}
}
if (checkTypes.isEmpty()) {
sender.sendMessage("No check types specified!");
return false;
}
Comparator<VLView> comparator = VLView.parseMixedComparator(args, startIndex);
if (comparator == null) {
// TODO: Default comparator ?
comparator = new FCFSComparator<ViolationHistory.VLView>(Arrays.asList(VLView.CmpnVL), true);
}
// Run a worker task.
Bukkit.getScheduler().scheduleSyncDelayedTask(access,
new PrimaryThreadWorker(sender, checkTypes, comparator, n, access));
return true;
}
// TODO: Tab completion (!).
}

View File

@ -35,6 +35,7 @@ public class Permissions {
public static final String COMMAND_RELOAD = COMMAND + ".reload";
public static final String COMMAND_REMOVEPLAYER = COMMAND + ".removeplayer";
public static final String COMMAND_RESET = COMMAND + ".reset";
public static final String COMMAND_TOP = COMMAND + ".top";
public static final String COMMAND_UNEXEMPT = COMMAND + ".unexempt";
public static final String COMMAND_VERSION = COMMAND + ".version";

View File

@ -19,6 +19,7 @@ commands:
# permissions: nocheatplus.admin.(...)
usage: |
Administrative commands overview:
/<command> top (entries) (check/s...) (sort by...) NEW.
/<command> info (player): Violation summary for a player.
/<command> inspect (player): Status info for a player.
/<command> notify on|off: In-game notifications per player.
@ -267,6 +268,8 @@ permissions:
nocheatplus.notify: true
nocheatplus.command.reload:
description: Allow the player to reload NoCheatPlus configuration.
nocheatplus.command.top:
description: Allow to search violation history for top violations.
nocheatplus.command.info:
description: Allow to see violation info about a player.
nocheatplus.command.inspect:
@ -328,6 +331,7 @@ permissions:
description: Info commands about players.
children:
nocheatplus.command.notify: true
nocheatplus.command.top: true
nocheatplus.command.info: true
nocheatplus.command.exemptions: true
nocheatplus.command.kicklist: true