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.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import fr.neatmonster.nocheatplus.hooks.APIUtils; import fr.neatmonster.nocheatplus.hooks.APIUtils;
import fr.neatmonster.nocheatplus.utilities.FCFSComparator;
/** /**
* The class containg the violation history of a player. * The class containg the violation history of a player.
@ -22,30 +27,29 @@ public class ViolationHistory {
* (Comparable by time.) * (Comparable by time.)
*/ */
public static class ViolationLevel{ public static class ViolationLevel{
/**
* Descending sort. /**
*/ * Descending sort by time.
public static Comparator<ViolationLevel> VLComparator = new Comparator<ViolationHistory.ViolationLevel>() { */
@Override public static Comparator<ViolationLevel> VLComparator = new Comparator<ViolationHistory.ViolationLevel>() {
public int compare(final ViolationLevel vl1, final ViolationLevel vl2) { @Override
if (vl1.time == vl2.time) return 0; public int compare(final ViolationLevel vl1, final ViolationLevel vl2) {
else if (vl1.time < vl2.time) return 1; return Long.compare(vl1.time, vl2.time);
else return -1; }
} };
};
/** The check. */ /** The check. */
public final String check; public final String check;
/** The sum of violation levels added. */ /** The sum of violation levels added. */
public double sumVL; public double sumVL;
/** Number of violations. */ /** Number of violations. */
public int nVL; public int nVL;
/** Maximal violation level added. */ /** Maximal violation level added. */
public double maxVL; public double maxVL;
/** The last VL time. */ /** The last VL time. */
public long time; public long time;
@ -79,20 +83,128 @@ public class ViolationHistory {
time = System.currentTimeMillis(); time = System.currentTimeMillis();
} }
@Override @Override
public boolean equals(final Object obj) { public boolean equals(final Object obj) {
// Might add String. // Might add String.
if (obj instanceof ViolationLevel) if (obj instanceof ViolationLevel)
return this.check.equals(((ViolationLevel) obj).check); return this.check.equals(((ViolationLevel) obj).check);
else return false; else return false;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return check.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)*/ /** Map the check string names to check types (workaround, keep at default, set by Check)*/
static Map<String, CheckType> checkTypeMap = new HashMap<String, CheckType>(); static Map<String, CheckType> checkTypeMap = new HashMap<String, CheckType>();
@ -110,7 +222,7 @@ public class ViolationHistory {
public static ViolationHistory getHistory(final Player player) { public static ViolationHistory getHistory(final Player player) {
return getHistory(player.getName(), true); return getHistory(player.getName(), true);
} }
/** /**
* Get the history of a player, create if desired and not present. * Get the history of a player, create if desired and not present.
* @param player * @param player
@ -122,7 +234,7 @@ public class ViolationHistory {
public static ViolationHistory getHistory(final Player player, final boolean create) { public static ViolationHistory getHistory(final Player player, final boolean create) {
return getHistory(player.getName(), create); return getHistory(player.getName(), create);
} }
/** /**
* Gets the history of a player by exact name. * Gets the history of a player by exact name.
* @param playerName * @param playerName
@ -132,43 +244,75 @@ public class ViolationHistory {
* @return * @return
*/ */
public static ViolationHistory getHistory(final String playerName, final boolean create) { public static ViolationHistory getHistory(final String playerName, final boolean create) {
final ViolationHistory hist = violationHistories.get(playerName); final ViolationHistory hist = violationHistories.get(playerName);
if (hist != null) if (hist != null)
return hist; return hist;
else if (create){ else if (create){
final ViolationHistory newHist = new ViolationHistory(); final ViolationHistory newHist = new ViolationHistory();
violationHistories.put(playerName, newHist); violationHistories.put(playerName, newHist);
return newHist; return newHist;
} }
else else
return null; 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){ public static ViolationHistory removeHistory(final String playerName){
return violationHistories.remove(playerName); return violationHistories.remove(playerName);
} }
public static void clear(final CheckType checkType){ public static void clear(final CheckType checkType){
for (ViolationHistory hist : violationHistories.values()){ for (ViolationHistory hist : violationHistories.values()){
hist.remove(checkType); hist.remove(checkType);
} }
} }
/** The violation levels for every check. */ /** The violation levels for every check. */
private final List<ViolationLevel> violationLevels = new ArrayList<ViolationLevel>(); private final List<ViolationLevel> violationLevels = new ArrayList<ViolationLevel>();
/** /**
* Gets the violation levels. * Gets the violation levels. Sorted by time, descending.
* *
* @return the violation levels * @return the violation levels
*/ */
public ViolationLevel[] getViolationLevels() { public ViolationLevel[] getViolationLevels() {
final ViolationLevel[] sortedLevels = new ViolationLevel[violationLevels.size()]; final ViolationLevel[] sortedLevels = new ViolationLevel[violationLevels.size()];
violationLevels.toArray(sortedLevels); violationLevels.toArray(sortedLevels);
Arrays.sort(sortedLevels, ViolationLevel.VLComparator); // Descending sort.; Arrays.sort(sortedLevels, ViolationLevel.VLComparator); // Descending sort.;
return sortedLevels; 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. * Log a VL.
* *
@ -191,23 +335,23 @@ public class ViolationHistory {
* @param checkType * @param checkType
* @return If entries were removed. * @return If entries were removed.
*/ */
public boolean remove(final CheckType checkType) { public boolean remove(final CheckType checkType) {
if (checkType == CheckType.ALL){ if (checkType == CheckType.ALL){
final boolean empty = violationLevels.isEmpty(); final boolean empty = violationLevels.isEmpty();
violationLevels.clear(); violationLevels.clear();
return !empty; return !empty;
} }
final Iterator<ViolationLevel> it = violationLevels.iterator(); final Iterator<ViolationLevel> it = violationLevels.iterator();
boolean found = false; boolean found = false;
while (it.hasNext()){ while (it.hasNext()){
final ViolationLevel vl = it.next(); final ViolationLevel vl = it.next();
final CheckType refType = checkTypeMap.get(vl.check); final CheckType refType = checkTypeMap.get(vl.check);
if (refType == null) continue; if (refType == null) continue;
if (refType == checkType || APIUtils.isParent(checkType, refType)){ if (refType == checkType || APIUtils.isParent(checkType, refType)){
found = true; found = true;
it.remove(); it.remove();
} }
} }
return found; 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.log.LogCommand;
import fr.neatmonster.nocheatplus.command.admin.notify.NotifyCommand; import fr.neatmonster.nocheatplus.command.admin.notify.NotifyCommand;
import fr.neatmonster.nocheatplus.command.admin.reset.ResetCommand; 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.components.INotifyReload;
import fr.neatmonster.nocheatplus.config.ConfPaths; import fr.neatmonster.nocheatplus.config.ConfPaths;
import fr.neatmonster.nocheatplus.config.ConfigFile; 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. * Instantiates a new command handler.
@ -78,45 +79,46 @@ public class NoCheatPlusCommand extends BaseCommand{
* the instance of NoCheatPlus * the instance of NoCheatPlus
*/ */
public NoCheatPlusCommand(final JavaPlugin plugin, final List<INotifyReload> notifyReload) { 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): // Register sub commands (special order):
for (BaseCommand cmd : new BaseCommand[]{ for (BaseCommand cmd : new BaseCommand[]{
new BanCommand(plugin), new BanCommand(plugin),
new CommandsCommand(plugin), new CommandsCommand(plugin),
new DelayCommand(plugin), new DelayCommand(plugin),
new ExemptCommand(plugin), new ExemptCommand(plugin),
new ExemptionsCommand(plugin), new ExemptionsCommand(plugin),
new InfoCommand(plugin), new TopCommand(plugin),
new InspectCommand(plugin), new InfoCommand(plugin),
new KickCommand(plugin), new InspectCommand(plugin),
new KickListCommand(plugin), new KickCommand(plugin),
new LagCommand(plugin), new KickListCommand(plugin),
new VersionCommand(plugin), new LagCommand(plugin),
new NotifyCommand(plugin), new VersionCommand(plugin),
new ReloadCommand(plugin, notifyReload), new NotifyCommand(plugin),
new RemovePlayerCommand(plugin), new ReloadCommand(plugin, notifyReload),
new TellCommand(plugin), new RemovePlayerCommand(plugin),
new DenyLoginCommand(plugin), new TellCommand(plugin),
new UnexemptCommand(plugin), new DenyLoginCommand(plugin),
new AllowLoginCommand(plugin), new UnexemptCommand(plugin),
new LogCommand(plugin), new AllowLoginCommand(plugin),
new ResetCommand(plugin), new LogCommand(plugin),
new ResetCommand(plugin),
}){ }){
addSubCommands(cmd); addSubCommands(cmd);
rootLabels.add(cmd.label); rootLabels.add(cmd.label);
} }
} }
/** /**
* Retrieve a collection with all sub-command permissions. * Retrieve a collection with all sub-command permissions.
* @return * @return
*/ */
public Collection<String> getAllSubCommandPermissions(){ public Collection<String> getAllSubCommandPermissions(){
final Set<String> set = new LinkedHashSet<String>(rootLabels.size()); final Set<String> set = new LinkedHashSet<String>(rootLabels.size());
for (final String label : rootLabels){ for (final String label : rootLabels){
set.add(subCommands.get(label).permission); set.add(subCommands.get(label).permission);
} }
return set; return set;
} }
/* (non-Javadoc) /* (non-Javadoc)
@ -126,82 +128,88 @@ public class NoCheatPlusCommand extends BaseCommand{
@Override @Override
public boolean onCommand(final CommandSender sender, final Command command, final String commandLabel, public boolean onCommand(final CommandSender sender, final Command command, final String commandLabel,
final String[] args) { final String[] args) {
if (!command.getName().equalsIgnoreCase("nocheatplus")){ if (!command.getName().equalsIgnoreCase("nocheatplus")){
// Not our command, how did it get here? // Not our command, how did it get here?
return false; return false;
} }
if (sender.hasPermission(Permissions.FILTER_COMMAND_NOCHEATPLUS)){ if (sender.hasPermission(Permissions.FILTER_COMMAND_NOCHEATPLUS)){
// Check sub-commands. // Check sub-commands.
if (args.length > 0){ if (args.length > 0){
AbstractCommand<?> subCommand = subCommands.get(args[0].trim().toLowerCase()); AbstractCommand<?> subCommand = subCommands.get(args[0].trim().toLowerCase());
if (subCommand != null && subCommand.testPermission(sender, command, commandLabel, args)){ if (subCommand != null && subCommand.testPermission(sender, command, commandLabel, args)){
// Sender has permission to run the command. // Sender has permission to run the command.
return subCommand.onCommand(sender, command, commandLabel, args); 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. // No sub command worked, print usage.
return false; return false;
} }
final ConfigFile config = ConfigManager.getConfigFile(); final ConfigFile config = ConfigManager.getConfigFile();
if (config.getBoolean(ConfPaths.PROTECT_PLUGINS_HIDE_ACTIVE)){ if (config.getBoolean(ConfPaths.PROTECT_PLUGINS_HIDE_ACTIVE)){
// Prevent the NCP usage printout: // Prevent the NCP usage printout:
// TODO: GetColoredString // TODO: GetColoredString
sender.sendMessage(ColorUtil.replaceColors(config.getString(ConfPaths.PROTECT_PLUGINS_HIDE_NOCOMMAND_MSG))); sender.sendMessage(ColorUtil.replaceColors(config.getString(ConfPaths.PROTECT_PLUGINS_HIDE_NOCOMMAND_MSG)));
return true; return true;
} }
else{ else{
return false; return false;
} }
} }
// /** // /**
// * Check which of the choices starts with prefix // * Check which of the choices starts with prefix
// * @param sender // * @param sender
// * @param choices // * @param choices
// * @return // * @return
// */ // */
// protected List<String> getTabMatches(CommandSender sender, Collection<String> choices, String prefix){ // protected List<String> getTabMatches(CommandSender sender, Collection<String> choices, String prefix){
// final List<String> res = new ArrayList<String>(choices.size()); // final List<String> res = new ArrayList<String>(choices.size());
// final Set<BaseCommand> done = new HashSet<BaseCommand>(); // final Set<BaseCommand> done = new HashSet<BaseCommand>();
// for (final String label : choices){ // for (final String label : choices){
// if (!label.startsWith(prefix)) continue; // if (!label.startsWith(prefix)) continue;
// final BaseCommand cmd = commands.get(label); // final BaseCommand cmd = commands.get(label);
// if (done.contains(cmd)) continue; // if (done.contains(cmd)) continue;
// done.add(cmd); // done.add(cmd);
// if (sender.hasPermission(cmd.permission)) res.add(cmd.label); // if (sender.hasPermission(cmd.permission)) res.add(cmd.label);
// } // }
// if (!res.isEmpty()){ // if (!res.isEmpty()){
// Collections.sort(res); // Collections.sort(res);
// return res; // return res;
// } // }
// return null; // return null;
// } // }
// @Override // @Override
// public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) // public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args)
// { // {
// // TODO: TabComplete check ? // // TODO: TabComplete check ?
// if (args.length == 0 || args.length == 1 && args[0].trim().isEmpty()){ // if (args.length == 0 || args.length == 1 && args[0].trim().isEmpty()){
// // Add labels without aliases. // // Add labels without aliases.
// return getTabMatches(sender, rootLabels, ""); // return getTabMatches(sender, rootLabels, "");
// } // }
// else { // else {
// final String subLabel = args[0].trim().toLowerCase(); // final String subLabel = args[0].trim().toLowerCase();
// if (args.length == 1){ // if (args.length == 1){
// // Also check aliases for matches. // // Also check aliases for matches.
// return getTabMatches(sender, commands.keySet(), subLabel); // return getTabMatches(sender, commands.keySet(), subLabel);
// } // }
// else{ // else{
// final NCPCommand cmd = commands.get(subLabel); // final NCPCommand cmd = commands.get(subLabel);
// if (cmd.testPermission...){ // if (cmd.testPermission...){
// // Delegate the tab-completion. // // Delegate the tab-completion.
// return cmd.onTabComplete(sender, command, alias, args); // return cmd.onTabComplete(sender, command, alias, args);
// } // }
// } // }
// } // }
// return null; // 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_RELOAD = COMMAND + ".reload";
public static final String COMMAND_REMOVEPLAYER = COMMAND + ".removeplayer"; public static final String COMMAND_REMOVEPLAYER = COMMAND + ".removeplayer";
public static final String COMMAND_RESET = COMMAND + ".reset"; 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_UNEXEMPT = COMMAND + ".unexempt";
public static final String COMMAND_VERSION = COMMAND + ".version"; public static final String COMMAND_VERSION = COMMAND + ".version";

View File

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