diff --git a/src/main/java/com/intellectualcrafters/plot/PS.java b/src/main/java/com/intellectualcrafters/plot/PS.java index 1855bd04c..93df9817f 100644 --- a/src/main/java/com/intellectualcrafters/plot/PS.java +++ b/src/main/java/com/intellectualcrafters/plot/PS.java @@ -1329,7 +1329,6 @@ public class PS { // Clearing + Expiry Settings.FAST_CLEAR = config.getBoolean("clear.fastmode"); Settings.DELETE_PLOTS_ON_BAN = config.getBoolean("clear.on.ban"); - Settings.MIN_BLOCKS_CHANGED_IGNORED = config.getInt("clear.ignore-if-modified"); Settings.AUTO_CLEAR_DAYS = config.getInt("clear.auto.days"); Settings.CLEAR_THRESHOLD = config.getInt("clear.auto.threshold"); Settings.AUTO_CLEAR = config.getBoolean("clear.auto.enabled"); diff --git a/src/main/java/com/intellectualcrafters/plot/commands/DebugExec.java b/src/main/java/com/intellectualcrafters/plot/commands/DebugExec.java index 83fe5eb2f..4dc47e404 100644 --- a/src/main/java/com/intellectualcrafters/plot/commands/DebugExec.java +++ b/src/main/java/com/intellectualcrafters/plot/commands/DebugExec.java @@ -58,17 +58,56 @@ public class DebugExec extends SubCommand { @Override public boolean execute(final PlotPlayer player, final String... args) { - final List allowed_params = Arrays.asList("analyze", "remove-flag", "stop-expire", "start-expire", "show-expired", "update-expired", "seen", "trim-check"); + final List allowed_params = Arrays.asList("calibrate-analysis", "remove-flag", "stop-expire", "start-expire", "show-expired", "update-expired", "seen", "trim-check"); if (args.length > 0) { final String arg = args[0].toLowerCase(); switch (arg) { case "analyze": { + if (player == null) { + MainUtil.sendMessage(player, C.IS_CONSOLE); + return false; + } + Plot plot = MainUtil.getPlot(player.getLocation()); + if (plot == null) { + MainUtil.sendMessage(player, C.NOT_IN_PLOT); + return false; + } + PlotAnalysis analysis = plot.getComplexity(); + if (analysis != null) { + int complexity = analysis.getComplexity(); + MainUtil.sendMessage(player, "Complexity: " + complexity); + return true; + } + MainUtil.sendMessage(player, "$1Starting task..."); + HybridUtils.manager.analyzePlot(plot, new RunnableVal() { + @Override + public void run() { + MainUtil.sendMessage(player, "$1Done: $2use $3/plot debugexec analyze$2 for more information"); + } + }); + return true; + } + case "calibrate-analysis": { + if (args.length != 2) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot debugexec analyze "); + MainUtil.sendMessage(player, "$1 $2= $1The percentage of plots you want to clear (100 clears 100% of plots so no point calibrating it)"); + return false; + } + double threshold; + try { + threshold = Integer.parseInt(args[1]) / 100d; + } + catch (NumberFormatException e) { + MainUtil.sendMessage(player, "$2Invalid threshold: " + args[1]); + MainUtil.sendMessage(player, "$1 $2= $1The percentage of plots you want to clear as a number between 0 - 100"); + return false; + } PlotAnalysis.calcOptimalModifiers(new Runnable() { @Override public void run() { - PS.log("&cTHIS FUNCTION IS A WORK IN PROGRESS"); + PS.log("$1Thank you for calibrating PlotSquared plot expiry"); } - }); + }, threshold); return true; } case "stop-expire": { diff --git a/src/main/java/com/intellectualcrafters/plot/commands/list.java b/src/main/java/com/intellectualcrafters/plot/commands/list.java index 655729afc..c5c291dfc 100644 --- a/src/main/java/com/intellectualcrafters/plot/commands/list.java +++ b/src/main/java/com/intellectualcrafters/plot/commands/list.java @@ -472,6 +472,8 @@ public class list extends SubCommand { .then("->") .color(ChatColor.GOLD) .command("/plot list " + args[0] + " " + (page + 2)) + .color(ChatColor.GRAY) + .then(C.CLICKABLE.s()) .send(((BukkitPlayer) player).player); return; } @@ -485,6 +487,8 @@ public class list extends SubCommand { .then("->") .color(ChatColor.GOLD) .command("/plot list " + args[0] + " " + (page + 2)) + .color(ChatColor.GRAY) + .then(C.CLICKABLE.s()) .send(((BukkitPlayer) player).player); return; } @@ -498,6 +502,8 @@ public class list extends SubCommand { .color(ChatColor.DARK_GRAY) .then("->") .color(ChatColor.DARK_GRAY) + .color(ChatColor.GRAY) + .then(C.CLICKABLE.s()) .send(((BukkitPlayer) player).player); return; } diff --git a/src/main/java/com/intellectualcrafters/plot/config/C.java b/src/main/java/com/intellectualcrafters/plot/config/C.java index c64226eea..43c07a10a 100644 --- a/src/main/java/com/intellectualcrafters/plot/config/C.java +++ b/src/main/java/com/intellectualcrafters/plot/config/C.java @@ -399,6 +399,7 @@ public enum C { * List */ COMMENT_LIST_HEADER_PAGED("$2(Page $1%cur$2/$1%max$2) $1List of %amount% comments", "List"), + CLICKABLE(" (interactive)", "List"), PLOT_LIST_HEADER_PAGED("$2(Page $1%cur$2/$1%max$2) $1List of %amount% plots", "List"), PLOT_LIST_HEADER("$1List of %word% plots", "List"), PLOT_LIST_ITEM("$2>> $1%id$2:$1%world $2- $1%owner", "List"), diff --git a/src/main/java/com/intellectualcrafters/plot/config/Settings.java b/src/main/java/com/intellectualcrafters/plot/config/Settings.java index 3f99334f3..e7d0d3f15 100644 --- a/src/main/java/com/intellectualcrafters/plot/config/Settings.java +++ b/src/main/java/com/intellectualcrafters/plot/config/Settings.java @@ -161,8 +161,7 @@ public class Settings { * Days until a plot gets cleared */ public static int AUTO_CLEAR_DAYS = 360; - public static int CLEAR_THRESHOLD = 100; - public static int MIN_BLOCKS_CHANGED_IGNORED = -1; + public static int CLEAR_THRESHOLD = -1; public static int CLEAR_INTERVAL = 120; /** * API Location diff --git a/src/main/java/com/intellectualcrafters/plot/object/PlotAnalysis.java b/src/main/java/com/intellectualcrafters/plot/object/PlotAnalysis.java index afb874e9c..85c004882 100644 --- a/src/main/java/com/intellectualcrafters/plot/object/PlotAnalysis.java +++ b/src/main/java/com/intellectualcrafters/plot/object/PlotAnalysis.java @@ -31,7 +31,7 @@ public class PlotAnalysis { public int air_sd; public int variety_sd; - public double complexity; + private int complexity; public static PlotAnalysis MODIFIERS = new PlotAnalysis(); @@ -52,30 +52,30 @@ public class PlotAnalysis { analysis.air_sd = values.get(8); analysis.variety_sd = values.get(9); - analysis.complexity = - + (analysis.changes) * MODIFIERS.changes - + (analysis.faces) * MODIFIERS.faces - + (analysis.data) * MODIFIERS.data - + (analysis.air) * MODIFIERS.air - + (analysis.variety) * MODIFIERS.variety - + (analysis.changes_sd) * MODIFIERS.changes_sd - + (analysis.faces_sd) * MODIFIERS.faces_sd - + (analysis.data_sd) * MODIFIERS.data_sd - + (analysis.air_sd) * MODIFIERS.air_sd - + (analysis.variety_sd) * MODIFIERS.variety_sd - ; + analysis.complexity = analysis.getComplexity(); return analysis; } return null; } - public static void analyzePlot(Plot plot, RunnableVal whenDone) { - PlotAnalysis analysis = getAnalysis(plot); - if (analysis != null) { - whenDone.value = analysis; - if (whenDone != null) whenDone.run(); - return; + public int getComplexity() { + if (complexity != 0) { + return complexity; } + complexity = (this.changes) * MODIFIERS.changes + + (this.faces) * MODIFIERS.faces + + (this.data) * MODIFIERS.data + + (this.air) * MODIFIERS.air + + (this.variety) * MODIFIERS.variety + + (this.changes_sd) * MODIFIERS.changes_sd + + (this.faces_sd) * MODIFIERS.faces_sd + + (this.data_sd) * MODIFIERS.data_sd + + (this.air_sd) * MODIFIERS.air_sd + + (this.variety_sd) * MODIFIERS.variety_sd; + return complexity; + } + + public static void analyzePlot(Plot plot, RunnableVal whenDone) { BukkitHybridUtils.manager.analyzePlot(plot, whenDone); } @@ -91,8 +91,8 @@ public class PlotAnalysis { PS.log("Calibration task already in progress!"); return; } - if (threshold < 0 || threshold >= 1) { - PS.log("Invalid threshold provided!"); + if (threshold <= 0 || threshold >= 1) { + PS.log("Invalid threshold provided! (Cannot be 0 or 100 as then there's no point calibrating)"); return; } running = true; @@ -295,13 +295,57 @@ public class PlotAnalysis { PlotAnalysis.MODIFIERS.variety_sd = factor_variety_sd == 1 ? 0 : (int) (factor_variety_sd * 1000 / MathMan.getMean(variety_sd)); PS.log(" - | variety_sd " + factor_variety_sd); + int[] complexity = new int[n]; - - PlotAnalysis analysis = getAnalysis(plots.get(Arrays.asList(rank_ratings).indexOf(optimal_index))); + PS.log(" $1Calculating threshold"); + int max = 0; + int min = 0; + for (int i = 0; i < n; i++) { + Plot plot = plots.get(i); + PlotAnalysis analysis = plot.getComplexity(); + complexity[i] = analysis.complexity; + if (analysis.complexity < min) { + min = analysis.complexity; + } + else if (analysis.complexity > max) { + max = analysis.complexity; + } + } + int optimal_complexity = Integer.MAX_VALUE; + if (min > 0 && max < 102400) { // If low size, use my fast ranking algorithm + int[] rank_complexity = rank(complexity, max + 1); + for (int i = 0; i < n; i++) { + if (rank_complexity[i] == optimal_index) { + optimal_complexity = complexity[i]; + break; + } + } + logln("Complexity: "); + logln(rank_complexity); + logln("Ratings: "); + logln(rank_ratings); + logln("Correlation: "); + logln(getCC(n, sum(square(getSD(rank_complexity, rank_ratings))))); + if (optimal_complexity == Integer.MAX_VALUE) { + PS.log(""); + running = false; + return; + } + } + else { // Use the fast radix sort algorithm + int[] sorted = complexity.clone(); + sort(sorted); + optimal_complexity = sorted[optimal_index]; + logln("Complexity: "); + logln(sorted); + logln("Ratings: "); + logln(rank_ratings); + } // Save calibration PS.log(" $1Saving calibration"); YamlConfiguration config = PS.get().config; + config.set("clear.auto.threshold", optimal_complexity); config.set("clear.auto.calibration.changes", PlotAnalysis.MODIFIERS.changes); config.set("clear.auto.calibration.faces", PlotAnalysis.MODIFIERS.faces); config.set("clear.auto.calibration.data", PlotAnalysis.MODIFIERS.data); @@ -318,13 +362,41 @@ public class PlotAnalysis { e.printStackTrace(); } - PS.log(" $1Done!"); + PS.log("$1Done!"); running = false; whenDone.run(); } }); } + public static void logln(Object obj) { + System.out.println(log(obj)); + } + + public static String log(Object obj) { + String result = ""; + if (obj.getClass().isArray()) { + String prefix = ""; + + for(int i=0; i) { + String prefix = ""; + for (Object element : (List) obj) { + result += prefix + log(element); + prefix = ","; + } + return "[ " + result + " ]"; + } + else { + return obj.toString(); + } + } + /** * Get correllation coefficient * @return @@ -390,15 +462,24 @@ public class PlotAnalysis { /** * An optimized algorithm for ranking a very specific set of inputs
- * - Input is an array of int with a max size of 102400 - * - This allows for optimizations beyond any standard sorting function + * - Input is an array of int with a max size of 102400
+ * - A reduced sample space allows for sorting (and ranking in this case) in linear time * @param input * @return */ public static int[] rank(final int[] input) { - int[] cache = new int[102400]; + return rank(input, 102400); + } + + /** + * An optimized algorithm for ranking a very specific set of inputs + * @param input + * @return + */ + public static int[] rank(final int[] input, int size) { + int[] cache = new int[size]; int max = 0; - if (input.length < 102400) { + if (input.length < size) { for (int value : input) { if (value > max) { max = value; diff --git a/src/main/java/com/intellectualcrafters/plot/util/ExpireManager.java b/src/main/java/com/intellectualcrafters/plot/util/ExpireManager.java index db77a880d..92f46d9cc 100644 --- a/src/main/java/com/intellectualcrafters/plot/util/ExpireManager.java +++ b/src/main/java/com/intellectualcrafters/plot/util/ExpireManager.java @@ -55,7 +55,7 @@ public class ExpireManager { public void run() { try { final List plots = getOldPlots(world); - PS.log("&7[&5Expire&dManager&7] &3Found " + plots.size() + " expired plots for " + world + "!"); + PS.log("$2[&5Expire&dManager$2] $4Found " + plots.size() + " expired plots for " + world + "!"); expiredPlots.put(world, plots); updatingPlots.put(world, false); } @@ -82,18 +82,18 @@ public class ExpireManager { } final Boolean updating = ExpireManager.updatingPlots.get(world); if (updating) { - PS.log("&7[&5Expire&dManager&7] &3Waiting on fetch..."); + PS.log("$2[&5Expire&dManager$2] $4Waiting on fetch..."); return; } if (!expiredPlots.containsKey(world)) { - PS.log("&7[&5Expire&dManager&7] &3Updating expired plots for: " + world); + PS.log("$2[&5Expire&dManager$2] $4Updating expired plots for: " + world); updateExpired(world); return; } final List plots = expiredPlots.get(world); if ((plots == null) || (plots.size() == 0)) { if (updateExpired(world)) { - PS.log("&7[&5Expire&dManager&7] &3Re-evaluating expired plots for: " + world); + PS.log("$2[&5Expire&dManager$2] $4Re-evaluating expired plots for: " + world); return; } continue; @@ -101,7 +101,7 @@ public class ExpireManager { final Plot plot = plots.iterator().next(); if (!isExpired(plot)) { expiredPlots.get(world).remove(plot); - PS.log("&7[&5Expire&dManager&7] &bSkipping no longer expired: " + plot); + PS.log("$2[&5Expire&dManager$2] &bSkipping no longer expired: " + plot); return; } for (final UUID helper : plot.trusted) { @@ -118,87 +118,53 @@ public class ExpireManager { } final PlotManager manager = PS.get().getPlotManager(world); if (manager == null) { - PS.log("&7[&5Expire&dManager&7] &cThis is a friendly reminder to create or delete " + world +" as it is currently setup incorrectly"); + PS.log("$2[&5Expire&dManager$2] &cThis is a friendly reminder to create or delete " + world +" as it is currently setup incorrectly"); expiredPlots.get(world).remove(plot); return; } final PlotWorld plotworld = PS.get().getPlotWorld(world); -// RunnableVal run = new RunnableVal() { -// @Override -// public void run() { -// int changed = this.value; -// if (Settings.MIN_BLOCKS_CHANGED_IGNORED > 0 || Settings.MIN_BLOCKS_CHANGED > 0 && manager instanceof ClassicPlotManager) { -// if (changed >= Settings.MIN_BLOCKS_CHANGED && Settings.MIN_BLOCKS_CHANGED > 0) { -// PS.log("&7[&5Expire&dManager&7] &bKeep flag added to: " + plot.id + (changed != -1 ? " (changed " + value + ")" : "")); -// FlagManager.addPlotFlag(plot, new Flag(FlagManager.getFlag("keep"), true)); -// expiredPlots.get(world).remove(plot); -// return; -// } -// else if (changed >= Settings.MIN_BLOCKS_CHANGED_IGNORED && Settings.MIN_BLOCKS_CHANGED_IGNORED > 0) { -// PS.log("&7[&5Expire&dManager&7] &bIgnoring modified plot: " + plot.id + (changed != -1 ? " (changed " + value + ")" : "")); -// FlagManager.addPlotFlag(plot, new Flag(FlagManager.getFlag("modified-blocks"), value)); -// expiredPlots.get(world).remove(plot); -// return; -// } -// } -// if (plot.settings.isMerged()) { -// MainUtil.unlinkPlot(plot); -// } -// plot.delete(); -// expiredPlots.get(world).remove(plot); -// PS.log("&7[&5Expire&dManager&7] &cDeleted expired plot: " + plot.id + (changed != -1 ? " (changed " + value + ")" : "")); -// PS.log("&3 - World: " + plot.world); -// if (plot.hasOwner()) { -// PS.log("&3 - Owner: " + UUIDHandler.getName(plot.owner)); -// } else { -// PS.log("&3 - Owner: Unowned"); -// } -// } -// }; - if ((Settings.MIN_BLOCKS_CHANGED_IGNORED > 0 || Settings.CLEAR_THRESHOLD != 100) && plotworld.TYPE == 0) { + RunnableVal run = new RunnableVal() { + @Override + public void run() { + PlotAnalysis changed = this.value; + if (Settings.CLEAR_THRESHOLD != -1 && plotworld.TYPE == 0 && changed != null) { + if (changed.getComplexity() > Settings.CLEAR_THRESHOLD) { + PS.log("$2[&5Expire&dManager$2] &bIgnoring modified plot: " + plot + " : " + changed.getComplexity() + " - " + changed.changes); + FlagManager.addPlotFlag(plot, new Flag(FlagManager.getFlag("modified-blocks"), value)); + expiredPlots.get(world).remove(plot); + return; + } + } + if (plot.settings.isMerged()) { + MainUtil.unlinkPlot(plot); + } + plot.delete(); + expiredPlots.get(world).remove(plot); + int complexity = changed == null ? 0 : changed.getComplexity(); + int modified = changed == null ? 0 : changed.changes; + PS.log("$2[&5Expire&dManager$2] &cDeleted expired plot: " + plot + " : " + complexity + " - " + modified); + PS.log("$4 - World: " + plot.world); + if (plot.hasOwner()) { + PS.log("$4 - Owner: " + UUIDHandler.getName(plot.owner)); + } else { + PS.log("$4 - Owner: Unowned"); + } + } + }; + if (Settings.CLEAR_THRESHOLD != -1 && plotworld.TYPE == 0) { PlotAnalysis analysis = plot.getComplexity(); if (analysis != null) { - /* - * TODO remove min blocks changed - * - it isn't an accurate way to determine plot complexity - * - * compare this plots complexity with every other plot: - * - If it is in the bottom (threshold)% then it will be cleared - * - That doesn't make sense, that would mean it would get significantly harder as time goes on. - * - I guess as time goes on you can become more strict? - * - * % of plots to clear - not sure how to do - * % within non cleared plots - doesn't work for first plot - * % of plots in clear queue - doesn't work if 1 plot - * - * could be determined during calibration - * - * or (faster) - * - * set threshold complexity during calibration - * - * ideal number of expired plots - * - * manually set complexity - */ - } - - - Flag flag = FlagManager.getPlotFlagAbs(plot, "analysis"); - if (flag != null) { - if ((Integer) flag.getValue() > Settings.MIN_BLOCKS_CHANGED_IGNORED) { - PS.log("&7[&5Expire&dManager&7] &bSkipping modified: " + plot); + if (analysis.getComplexity() > Settings.CLEAR_THRESHOLD) { + PS.log("$2[&5Expire&dManager$2] &bSkipping modified: " + plot); expiredPlots.get(world).remove(plot); this.run(); return; } } - else { - HybridUtils.manager.checkModified(plot, run); - } + HybridUtils.manager.analyzePlot(plot, run); } else { - run.value = -1; + run.value = null; run.run(); } return; @@ -271,42 +237,6 @@ public class ExpireManager { continue; } if (isExpired(plot)) { - if (Settings.AUTO_CLEAR_CHECK_DISK) { - final String worldname = Bukkit.getWorlds().get(0).getName(); - String foldername; - String filename = null; - if (PS.get().IMP.checkVersion(1, 7, 5)) { - foldername = "playerdata"; - try { - final OfflinePlotPlayer op = UUIDHandler.uuidWrapper.getOfflinePlayer(uuid); - filename = op.getUUID() + ".dat"; - } catch (final Throwable e) { - filename = uuid.toString() + ".dat"; - } - } else { - foldername = "players"; - final String playername = UUIDHandler.getName(uuid); - if (playername != null) { - filename = playername + ".dat"; - } - } - if (filename != null) { - final File playerFile = new File(worldname + File.separator + foldername + File.separator + filename); - if (!playerFile.exists()) { - PS.log("Could not find file: " + filename); - } else { - try { - long last = playerFile.lastModified(); - long compared = System.currentTimeMillis() - last; - if (compared < (86400000l * Settings.AUTO_CLEAR_DAYS)) { - continue; - } - } catch (final Exception e) { - PS.log("Please disable disk checking in old plot auto clearing; Could not read file: " + filename); - } - } - } - } toRemove.add(plot); } }