diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/api/PlotAPI.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/api/PlotAPI.java new file mode 100644 index 000000000..c966903be --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/api/PlotAPI.java @@ -0,0 +1,224 @@ +package com.github.intellectualsites.plotsquared.api; + +import com.github.intellectualsites.plotsquared.configuration.file.YamlConfiguration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.flag.Flag; +import com.github.intellectualsites.plotsquared.plot.flag.Flags; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotArea; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.ChunkManager; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.SchematicHandler; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; +import com.github.intellectualsites.plotsquared.plot.util.block.GlobalBlockQueue; +import com.github.intellectualsites.plotsquared.plot.uuid.UUIDWrapper; +import lombok.NoArgsConstructor; + +import java.util.Collections; +import java.util.Set; +import java.util.UUID; + +/** + * PlotSquared API. + * + *

Useful classes: + *

+ * + * @version 3.3.3 + */ +@SuppressWarnings({"unused", "WeakerAccess"}) @NoArgsConstructor public class PlotAPI { + + /** + * Get all plots. + * + * @return all plots + * @see PlotSquared#getPlots() + */ + public Set getAllPlots() { + return PlotSquared.get().getPlots(); + } + + /** + * Return all plots for a player. + * + * @param player Player, whose plots to search for + * @return all plots that a player owns + */ + public Set getPlayerPlots(PlotPlayer player) { + return PlotSquared.get().getPlots(player); + } + + /** + * Add a plot world. + * + * @param plotArea Plot World Object + * @see PlotSquared#addPlotArea(PlotArea) + */ + public void addPlotArea(PlotArea plotArea) { + PlotSquared.get().addPlotArea(plotArea); + } + + /** + * Returns the PlotSquared configurations file. + * + * @return main configuration + * @see PlotSquared#config + */ + public YamlConfiguration getConfig() { + return PlotSquared.get().config; + } + + /** + * Get the PlotSquared storage file. + * + * @return storage configuration + * @see PlotSquared#storage + */ + public YamlConfiguration getStorage() { + return PlotSquared.get().storage; + } + + /** + * Get the main class for this plugin. Only use this if you really need it. + * + * @return PlotSquared PlotSquared Main Class + * @see PlotSquared + */ + public PlotSquared getMain() { + return PlotSquared.get(); + } + + /** + * ChunkManager class contains several useful methods. + * + * + * @return ChunkManager + * @see ChunkManager + */ + public ChunkManager getChunkManager() { + return ChunkManager.manager; + } + + /** + * Get the block/biome set queue + * + * @return GlobalBlockQueue.IMP + */ + public GlobalBlockQueue getBlockQueue() { + return GlobalBlockQueue.IMP; + } + + /** + * UUIDWrapper class has basic methods for getting UUIDS. It's recommended + * to use the UUIDHandler class instead. + * + * @return UUIDWrapper + * @see UUIDWrapper + */ + public UUIDWrapper getUUIDWrapper() { + return UUIDHandler.getUUIDWrapper(); + } + + /** + * SchematicHandler class contains methods related to pasting, reading + * and writing schematics. + * + * @return SchematicHandler + * @see SchematicHandler + */ + public SchematicHandler getSchematicHandler() { + return SchematicHandler.manager; + } + + /** + * Get a list of PlotAreas in the world. + * + * @param world The world to check for plot areas + * @return A set of PlotAreas + */ + public Set getPlotAreas(String world) { + if (world == null) { + return Collections.emptySet(); + } + return PlotSquared.get().getPlotAreas(world); + } + + /** + * Send a message to the console. The message supports color codes. + * + * @param message the message + * @see MainUtil#sendConsoleMessage(C, String...) + */ + public void sendConsoleMessage(String message) { + PlotSquared.log(message); + } + + /** + * Send a message to the console. + * + * @param caption the message + * @see #sendConsoleMessage(String) + * @see C + */ + public void sendConsoleMessage(C caption) { + sendConsoleMessage(caption.s()); + } + + /** + * Registers a flag for use in plots. + * + * @param flag the flag to register + */ + public void addFlag(Flag flag) { + Flags.registerFlag(flag); + } + + /** + * Gets the PlotSquared class. + * + * @return PlotSquared Class + * @see PlotSquared + */ + public PlotSquared getPlotSquared() { + return PlotSquared.get(); + } + + /** + * Get the PlotPlayer for a UUID. + * + *

Please note that PlotSquared can be configured to provide + * different UUIDs than bukkit + * + * @param uuid the uuid of the player to wrap + * @return a {@code PlotPlayer} + * @see PlotPlayer#wrap(Object) + */ + public PlotPlayer wrapPlayer(UUID uuid) { + return PlotPlayer.wrap(uuid); + } + + /** + * Get the PlotPlayer for a username. + * + * @param player the player to wrap + * @return a {@code PlotPlayer} + * @see PlotPlayer#wrap(Object) + */ + public PlotPlayer wrapPlayer(String player) { + return PlotPlayer.wrap(player); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/Argument.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/Argument.java new file mode 100644 index 000000000..dd5221a3a --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/Argument.java @@ -0,0 +1,68 @@ +package com.github.intellectualsites.plotsquared.commands; + +import com.github.intellectualsites.plotsquared.plot.object.PlotId; + +public abstract class Argument { + + public static final Argument Integer = new Argument("int", 16) { + @Override public Integer parse(String in) { + Integer value = null; + try { + value = java.lang.Integer.parseInt(in); + } catch (Exception ignored) { + } + return value; + } + }; + public static final Argument Boolean = new Argument("boolean", true) { + @Override public Boolean parse(String in) { + Boolean value = null; + if (in.equalsIgnoreCase("true") || in.equalsIgnoreCase("Yes") || in + .equalsIgnoreCase("1")) { + value = true; + } else if (in.equalsIgnoreCase("false") || in.equalsIgnoreCase("No") || in + .equalsIgnoreCase("0")) { + value = false; + } + return value; + } + }; + public static final Argument String = new Argument("String", "Example") { + @Override public String parse(String in) { + return in; + } + }; + public static final Argument PlayerName = + new Argument("PlayerName", "Dinnerbone") { + @Override public String parse(String in) { + return in.length() <= 16 ? in : null; + } + }; + public static final Argument PlotID = + new Argument("PlotID", new PlotId(-6, 3)) { + @Override public PlotId parse(String in) { + return PlotId.fromString(in); + } + }; + private final String name; + private final T example; + + public Argument(String name, T example) { + this.name = name; + this.example = example; + } + + public abstract T parse(String in); + + @Override public final String toString() { + return this.getName(); + } + + public final String getName() { + return this.name; + } + + public final T getExample() { + return this.example; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/Command.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/Command.java new file mode 100644 index 000000000..6bac09a26 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/Command.java @@ -0,0 +1,596 @@ +package com.github.intellectualsites.plotsquared.commands; + +import com.github.intellectualsites.plotsquared.configuration.file.YamlConfiguration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.commands.CommandCategory; +import com.github.intellectualsites.plotsquared.plot.commands.MainCommand; +import com.github.intellectualsites.plotsquared.plot.commands.RequiredType; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.PlotMessage; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal2; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal3; +import com.github.intellectualsites.plotsquared.plot.util.*; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +public abstract class Command { + + // May be none + private final ArrayList allCommands = new ArrayList<>(); + private final ArrayList dynamicCommands = new ArrayList<>(); + private final HashMap staticCommands = new HashMap<>(); + + // Parent command (may be null) + private final Command parent; + private final boolean isStatic; + // The command ID + private String id; + private List aliases; + private RequiredType required; + private String usage; + private String description; + private String perm; + private boolean confirmation; + private CommandCategory category; + private Argument[] arguments; + + public Command(Command parent, boolean isStatic, String id, String perm, RequiredType required, + CommandCategory cat) { + this.parent = parent; + this.isStatic = isStatic; + this.id = id; + this.perm = perm; + this.required = required; + this.category = cat; + this.aliases = Arrays.asList(id); + if (this.parent != null) { + this.parent.register(this); + } + } + + public Command(Command parent, boolean isStatic) { + this.parent = parent; + this.isStatic = isStatic; + Annotation cdAnnotation = getClass().getAnnotation(CommandDeclaration.class); + if (cdAnnotation != null) { + CommandDeclaration declaration = (CommandDeclaration) cdAnnotation; + init(declaration); + } + for (final Method method : getClass().getDeclaredMethods()) { + if (method.isAnnotationPresent(CommandDeclaration.class)) { + Class[] types = method.getParameterTypes(); + // final PlotPlayer player, String[] args, RunnableVal3 confirm, RunnableVal2 + // whenDone + if (types.length == 5 && types[0] == Command.class && types[1] == PlotPlayer.class + && types[2] == String[].class && types[3] == RunnableVal3.class + && types[4] == RunnableVal2.class) { + Command tmp = new Command(this, true) { + @Override public void execute(PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) { + try { + method.invoke(Command.this, this, player, args, confirm, whenDone); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + } + }; + tmp.init(method.getAnnotation(CommandDeclaration.class)); + } + } + } + } + + public Command getParent() { + return this.parent; + } + + public String getId() { + return this.id; + } + + public String getFullId() { + if (this.parent != null && this.parent.getParent() != null) { + return this.parent.getFullId() + "." + this.id; + } + return this.id; + } + + public List getCommands(PlotPlayer player) { + List commands = new ArrayList<>(); + for (Command cmd : this.allCommands) { + if (cmd.canExecute(player, false)) { + commands.add(cmd); + } + } + return commands; + } + + public List getCommands(CommandCategory cat, PlotPlayer player) { + List commands = getCommands(player); + if (cat != null) { + Iterator iterator = commands.iterator(); + while (iterator.hasNext()) { + if (iterator.next().category != cat) { + iterator.remove(); + } + } + } + return commands; + } + + public List getCommands() { + return this.allCommands; + } + + public boolean hasConfirmation(PlotPlayer player) { + return this.confirmation && !player.hasPermission(getPermission() + ".confirm.bypass"); + } + + public List getAliases() { + return this.aliases; + } + + public String getDescription() { + return this.description; + } + + public RequiredType getRequiredType() { + return this.required; + } + + public Argument[] getRequiredArguments() { + return this.arguments; + } + + public void setRequiredArguments(Argument[] arguments) { + this.arguments = arguments; + } + + public void init(CommandDeclaration declaration) { + this.id = declaration.command(); + this.perm = declaration.permission(); + this.required = declaration.requiredType(); + this.category = declaration.category(); + HashMap options = new HashMap<>(); + List aliasOptions = new ArrayList<>(); + aliasOptions.add(this.id); + aliasOptions.addAll(Arrays.asList(declaration.aliases())); + options.put("aliases", aliasOptions); + options.put("description", declaration.description()); + options.put("usage", declaration.usage()); + options.put("confirmation", declaration.confirmation()); + boolean set = false; + YamlConfiguration commands = + PlotSquared.get() == null ? new YamlConfiguration() : PlotSquared.get().commands; + for (Map.Entry entry : options.entrySet()) { + String key = this.getFullId() + "." + entry.getKey(); + if (!commands.contains(key)) { + commands.set(key, entry.getValue()); + set = true; + } + } + if (set && PlotSquared.get() != null) { + try { + commands.save(PlotSquared.get().commandsFile); + } catch (IOException e) { + e.printStackTrace(); + + } + } + this.aliases = commands.getStringList(this.getFullId() + ".aliases"); + this.description = commands.getString(this.getFullId() + ".description"); + this.usage = commands.getString(this.getFullId() + ".usage"); + this.confirmation = commands.getBoolean(this.getFullId() + ".confirmation"); + if (this.parent != null) { + this.parent.register(this); + } + } + + public void register(Command command) { + if (command.isStatic) { + for (String alias : command.aliases) { + this.staticCommands.put(alias.toLowerCase(), command); + } + } else { + this.dynamicCommands.add(command); + } + this.allCommands.add(command); + } + + public String getPermission() { + if (this.perm != null && !this.perm.isEmpty()) { + return this.perm; + } + if (this.parent == null) { + return "plots.use"; + } + return "plots." + getFullId(); + } + + public void paginate(PlotPlayer player, List c, int size, int page, + RunnableVal3 add, String baseCommand, String header) { + // Calculate pages & index + if (page < 0) { + page = 0; + } + int totalPages = (int) Math.ceil(c.size() / size); + if (page > totalPages) { + page = totalPages; + } + int max = page * size + size; + if (max > c.size()) { + max = c.size(); + } + // Send the header + header = header.replaceAll("%cur", page + 1 + "").replaceAll("%max", totalPages + 1 + "") + .replaceAll("%amount%", c.size() + "").replaceAll("%word%", "all"); + MainUtil.sendMessage(player, header); + // Send the page content + List subList = c.subList(page * size, max); + int i = page * size; + for (T obj : subList) { + i++; + PlotMessage msg = new PlotMessage(); + add.run(i, obj, msg); + msg.send(player); + } + // Send the footer + if (page < totalPages && page > 0) { // Back | Next + new PlotMessage().text("<-").color("$1").command(baseCommand + " " + page).text(" | ") + .color("$3").text("->").color("$1").command(baseCommand + " " + (page + 2)) + .text(C.CLICKABLE.s()).color("$2").send(player); + return; + } + if (page == 0 && totalPages != 0) { // Next + new PlotMessage().text("<-").color("$3").text(" | ").color("$3").text("->").color("$1") + .command(baseCommand + " " + (0 + 2)).text(C.CLICKABLE.s()).color("$2") + .send(player); + return; + } + if (page == totalPages && totalPages != 0) { // Back + new PlotMessage().text("<-").color("$1").command(baseCommand + " " + page).text(" | ") + .color("$3").text("->").color("$3").text(C.CLICKABLE.s()).color("$2").send(player); + } + } + + /** + * @param player Caller + * @param args Arguments + * @param confirm Instance, Success, Failure + * @return + */ + public void execute(PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) throws CommandException { + if (args.length == 0 || args[0] == null) { + if (this.parent == null) { + MainCommand.getInstance().help.displayHelp(player, null, 0); + } else { + C.COMMAND_SYNTAX.send(player, getUsage()); + } + return; + } + if (this.allCommands == null || this.allCommands.isEmpty()) { + player.sendMessage( + "Not Implemented: https://github.com/IntellectualSites/PlotSquared/issues/new"); + return; + } + Command cmd = getCommand(args[0]); + if (cmd == null) { + if (this.parent != null) { + C.COMMAND_SYNTAX.send(player, getUsage()); + return; + } + // Help command + try { + if (args.length == 0 || MathMan.isInteger(args[0]) + || CommandCategory.valueOf(args[0].toUpperCase()) != null) { + // This will default certain syntax to the help command + // e.g. /plot, /plot 1, /plot claiming + MainCommand.getInstance().help.execute(player, args, null, null); + return; + } + } catch (IllegalArgumentException ignored) { + } + // Command recommendation + MainUtil.sendMessage(player, C.NOT_VALID_SUBCOMMAND); + List commands = getCommands(player); + if (commands.isEmpty()) { + MainUtil + .sendMessage(player, C.DID_YOU_MEAN, MainCommand.getInstance().help.getUsage()); + return; + } + HashSet setargs = new HashSet<>(args.length); + for (String arg : args) { + setargs.add(arg.toLowerCase()); + } + String[] allargs = setargs.toArray(new String[setargs.size()]); + int best = 0; + for (Command current : commands) { + int match = getMatch(allargs, current); + if (match > best) { + cmd = current; + } + } + if (cmd == null) { + cmd = new StringComparison<>(args[0], this.allCommands).getMatchObject(); + } + MainUtil.sendMessage(player, C.DID_YOU_MEAN, cmd.getUsage()); + return; + } + String[] newArgs = Arrays.copyOfRange(args, 1, args.length); + if (!cmd.checkArgs(player, newArgs) || !cmd.canExecute(player, true)) { + return; + } + try { + cmd.execute(player, newArgs, confirm, whenDone); + } catch (CommandException e) { + e.perform(player); + } + } + + public boolean checkArgs(PlotPlayer player, String[] args) { + Argument[] reqArgs = getRequiredArguments(); + if (reqArgs != null && reqArgs.length > 0) { + boolean failed = args.length < reqArgs.length; + String[] baseSplit = getCommandString().split(" "); + String[] fullSplit = getUsage().split(" "); + String base = getCommandString(); + if (fullSplit.length - baseSplit.length < reqArgs.length) { + String[] tmp = new String[baseSplit.length + reqArgs.length]; + System.arraycopy(fullSplit, 0, tmp, 0, fullSplit.length); + fullSplit = tmp; + } + for (int i = 0; i < reqArgs.length; i++) { + fullSplit[i + baseSplit.length] = reqArgs[i].getExample().toString(); + failed = failed || reqArgs[i].parse(args[i]) == null; + } + if (failed) { + C.COMMAND_SYNTAX.send(player, StringMan.join(fullSplit, " ")); + return false; + } + } + return true; + } + + public int getMatch(String[] args, Command cmd) { + int count = 0; + String perm = cmd.getPermission(); + HashSet desc = new HashSet<>(); + for (String alias : cmd.getAliases()) { + if (alias.startsWith(args[0])) { + count += 5; + } + } + Collections.addAll(desc, cmd.getDescription().split(" ")); + for (String arg : args) { + if (perm.startsWith(arg)) { + count++; + } + if (desc.contains(arg)) { + count++; + } + } + String[] usage = cmd.getUsage().split(" "); + for (int i = 0; i < Math.min(4, usage.length); i++) { + int require; + if (usage[i].startsWith("<")) { + require = 1; + } else { + require = 0; + } + String[] split = usage[i].split("\\|| |\\>|\\<|\\[|\\]|\\{|\\}|\\_|\\/"); + for (String aSplit : split) { + for (String arg : args) { + if (StringMan.isEqualIgnoreCase(arg, aSplit)) { + count += 5 - i + require; + } + } + } + } + count += StringMan.intersection(desc, args); + return count; + } + + public Command getCommand(String arg) { + Command cmd = this.staticCommands.get(arg.toLowerCase()); + if (cmd == null) { + for (Command command : this.dynamicCommands) { + if (command.matches(arg)) { + return command; + } + } + } + return cmd; + } + + public Command getCommand(Class clazz) { + for (Command cmd : this.allCommands) { + if (cmd.getClass() == clazz) { + return cmd; + } + } + return null; + } + + public Command getCommandById(String id) { + Command exact = this.staticCommands.get(id); + if (exact != null) { + return exact; + } + for (Command cmd : this.allCommands) { + if (cmd.getId().equals(id)) { + return cmd; + } + } + return null; + } + + public boolean canExecute(PlotPlayer player, boolean message) { + if (player == null) { + return true; + } + if (!this.required.allows(player)) { + if (message) { + MainUtil.sendMessage(player, + this.required == RequiredType.PLAYER ? C.IS_CONSOLE : C.NOT_CONSOLE); + } + } else if (!Permissions.hasPermission(player, getPermission())) { + if (message) { + C.NO_PERMISSION.send(player, getPermission()); + } + } else { + return true; + } + return false; + } + + public boolean matches(String arg) { + arg = arg.toLowerCase(); + return StringMan.isEqual(arg, this.id) || this.aliases.contains(arg); + } + + public String getCommandString() { + String base; + if (this.parent == null) { + return "/" + toString(); + } else { + return this.parent.getCommandString() + " " + toString(); + } + } + + public String getUsage() { + if (this.usage != null && !this.usage.isEmpty()) { + if (this.usage.startsWith("/")) { + return this.usage; + } + return getCommandString() + " " + this.usage; + } + if (this.allCommands.isEmpty()) { + return getCommandString(); + } + StringBuilder args = new StringBuilder("["); + String prefix = ""; + for (Command cmd : this.allCommands) { + args.append(prefix).append(cmd.isStatic ? cmd.toString() : "<" + cmd + ">"); + prefix = "|"; + } + return getCommandString() + " " + args + "]"; + } + + public Collection tabOf(PlotPlayer player, String[] input, boolean space, + String... args) { + if (!space) { + return null; + } + List result = new ArrayList<>(); + int index = input.length - (space ? 0 : 1); + for (String arg : args) { + arg = arg.replace(getCommandString() + " ", ""); + String[] split = arg.split(" "); + if (split.length <= index) { + continue; + } + arg = StringMan.join(Arrays.copyOfRange(split, index, split.length), " "); + Command cmd = new Command(null, false, arg, getPermission(), getRequiredType(), null) { + }; + result.add(cmd); + } + return result; + } + + public Collection tab(PlotPlayer player, String[] args, boolean space) { + switch (args.length) { + case 0: + return this.allCommands; + case 1: + String arg = args[0].toLowerCase(); + if (space) { + Command cmd = getCommand(arg); + if (cmd != null && cmd.canExecute(player, false)) { + return cmd.tab(player, Arrays.copyOfRange(args, 1, args.length), space); + } else { + return null; + } + } else { + Set commands = new HashSet(); + for (Map.Entry entry : this.staticCommands.entrySet()) { + if (entry.getKey().startsWith(arg) && entry.getValue() + .canExecute(player, false)) { + commands.add(entry.getValue()); + } + } + return commands; + } + default: + Command cmd = getCommand(args[0]); + if (cmd != null) { + return cmd.tab(player, Arrays.copyOfRange(args, 1, args.length), space); + } else { + return null; + } + } + } + + @Override public String toString() { + return !this.aliases.isEmpty() ? this.aliases.get(0) : this.id; + } + + @Override public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (getClass() != obj.getClass()) { + return false; + } + Command other = (Command) obj; + if (this.hashCode() != other.hashCode()) { + return false; + } + return this.getFullId().equals(other.getFullId()); + } + + @Override public int hashCode() { + return this.getFullId().hashCode(); + } + + public void checkTrue(boolean mustBeTrue, C message, Object... args) { + if (!mustBeTrue) { + throw new CommandException(message, args); + } + } + + public T check(T object, C message, Object... args) { + if (object == null) { + throw new CommandException(message, args); + } + return object; + } + + public enum CommandResult { + FAILURE, SUCCESS + } + + + public static class CommandException extends RuntimeException { + private final Object[] args; + private final C message; + + public CommandException(C message, Object... args) { + this.message = message; + this.args = args; + } + + public void perform(PlotPlayer player) { + if (player != null && message != null) { + message.send(player, args); + } + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/CommandCaller.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/CommandCaller.java new file mode 100644 index 000000000..3b5ee295e --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/CommandCaller.java @@ -0,0 +1,24 @@ +package com.github.intellectualsites.plotsquared.commands; + +import com.github.intellectualsites.plotsquared.plot.commands.RequiredType; + +public interface CommandCaller { + + /** + * Send the player a message. + * + * @param message the message to send + */ + void sendMessage(String message); + + /** + * Check the player's permissions. Will be cached if permission caching is enabled. + * + * @param permission the name of the permission + */ + boolean hasPermission(String permission); + + boolean isPermissionSet(String permission); + + RequiredType getSuperCaller(); +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/CommandDeclaration.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/CommandDeclaration.java new file mode 100644 index 000000000..851a03174 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/commands/CommandDeclaration.java @@ -0,0 +1,29 @@ +package com.github.intellectualsites.plotsquared.commands; + +import com.github.intellectualsites.plotsquared.plot.commands.CommandCategory; +import com.github.intellectualsites.plotsquared.plot.commands.RequiredType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) +public @interface CommandDeclaration { + + String command(); + + String[] aliases() default {}; + + String permission() default ""; + + String usage() default ""; + + String description() default ""; + + RequiredType requiredType() default RequiredType.NONE; + + CommandCategory category() default CommandCategory.INFO; + + boolean confirmation() default false; +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/Configuration.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/Configuration.java new file mode 100644 index 000000000..9d694c1ec --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/Configuration.java @@ -0,0 +1,87 @@ +package com.github.intellectualsites.plotsquared.configuration; + +import javax.annotation.Nonnull; +import java.util.Map; + +/** + * Represents a source of configurable options and settings. + */ +public interface Configuration extends ConfigurationSection { + /** + * Sets the default value of the given path as provided. + *

+ *

If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default value.

+ *

+ *

If value is null, the value will be removed from the default + * Configuration source.

+ * + * @param path Path of the value to set. + * @param value Value to set the default to. + * @throws IllegalArgumentException Thrown if path is null. + */ + @Override void addDefault(@Nonnull String path, Object value); + + /** + * Sets the default values of the given paths as provided. + *

+ *

If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default values.

+ * + * @param defaults A map of Path->Values to add to defaults. + * @throws IllegalArgumentException Thrown if defaults is null. + */ + void addDefaults(Map defaults); + + /** + * Sets the default values of the given paths as provided. + *

+ *

If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default value.

+ *

+ *

This method will not hold a reference to the specified Configuration, + * nor will it automatically update if that Configuration ever changes. If + * you check this, you should set the default source with {@link + * #setDefaults(Configuration)}.

+ * + * @param defaults A configuration holding a list of defaults to copy. + * @throws IllegalArgumentException Thrown if defaults is null or this. + */ + void addDefaults(Configuration defaults); + + /** + * Gets the source {@link Configuration} for this configuration. + *

+ *

+ * If no configuration source was set, but default values were added, then + * a {@link MemoryConfiguration} will be returned. If no source was set + * and no defaults were set, then this method will return null.

+ * + * @return Configuration source for default values, or null if none exist. + */ + Configuration getDefaults(); + + /** + * Sets the source of all default values for this {@link Configuration}. + *

+ *

+ * If a previous source was set, or previous default values were defined, + * then they will not be copied to the new source.

+ * + * @param defaults New source of default values for this configuration. + * @throws IllegalArgumentException Thrown if defaults is null or this. + */ + void setDefaults(Configuration defaults); + + /** + * Gets the {@link ConfigurationOptions} for this {@link Configuration}. + *

+ *

All setters through this method are chainable.

+ * + * @return Options for this configuration + */ + ConfigurationOptions options(); +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/ConfigurationOptions.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/ConfigurationOptions.java new file mode 100644 index 000000000..f86016031 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/ConfigurationOptions.java @@ -0,0 +1,90 @@ +package com.github.intellectualsites.plotsquared.configuration; + +/** + * Various settings for controlling the input and output of a {@link + * Configuration}. + */ +class ConfigurationOptions { + private final Configuration configuration; + private char pathSeparator = '.'; + private boolean copyDefaults = false; + + protected ConfigurationOptions(Configuration configuration) { + this.configuration = configuration; + } + + /** + * Returns the {@link Configuration} that this object is responsible for. + * + * @return Parent configuration + */ + public Configuration configuration() { + return configuration; + } + + /** + * Gets the char that will be used to separate {@link + * ConfigurationSection}s. + * + *

This value does not affect how the {@link Configuration} is stored, + * only in how you access the data. The default value is '.'. + * + * @return Path separator + */ + char pathSeparator() { + return pathSeparator; + } + + /** + * Sets the char that will be used to separate {@link + * ConfigurationSection}s. + * + *

This value does not affect how the {@link Configuration} is stored, + * only in how you access the data. The default value is '.'. + * + * @param value Path separator + * @return This object, for chaining + */ + public ConfigurationOptions pathSeparator(char value) { + pathSeparator = value; + return this; + } + + /** + * Checks if the {@link Configuration} should copy values from its default + * {@link Configuration} directly. + * + *

If this is true, all values in the default Configuration will be + * directly copied, making it impossible to distinguish between values + * that were set and values that are provided by default. As a result, + * {@link ConfigurationSection#contains(String)} will always + * return the same value as {@link + * ConfigurationSection#isSet(String)}. The default value is + * false. + * + * @return Whether or not defaults are directly copied + */ + boolean copyDefaults() { + return copyDefaults; + } + + /** + * Sets if the {@link Configuration} should copy values from its default + * {@link Configuration} directly. + * + *

If this is true, all values in the default Configuration will be + * directly copied, making it impossible to distinguish between values + * that were set and values that are provided by default. As a result, + * {@link ConfigurationSection#contains(String)} will always + * return the same value as {@link + * ConfigurationSection#isSet(String)}. The default value is + * false. + * + * @param value Whether or not defaults are directly copied + * @return This object, for chaining + */ + public ConfigurationOptions copyDefaults(boolean value) { + copyDefaults = value; + return this; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/ConfigurationSection.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/ConfigurationSection.java new file mode 100644 index 000000000..9e15a9030 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/ConfigurationSection.java @@ -0,0 +1,649 @@ +package com.github.intellectualsites.plotsquared.configuration; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Represents a section of a {@link Configuration}. + */ +public interface ConfigurationSection { + + /** + * Gets a set containing all keys in this section. + * + *

If deep is set to true, then this will contain all the keys within any + * child {@link ConfigurationSection}s (and their children, etc). These + * will be in a valid path notation for you to use. + * + *

If deep is set to false, then this will contain only the keys of any + * direct children, and not their own children. + * + * @param deep Whether or not to get a deep list, as opposed to a shallow + * list. + * @return Set of keys contained within this ConfigurationSection. + */ + Set getKeys(boolean deep); + + /** + * Gets a Map containing all keys and their values for this section. + * + *

If deep is set to true, then this will contain all the keys and values + * within any child {@link ConfigurationSection}s (and their children, + * etc). These keys will be in a valid path notation for you to use. + * + *

If deep is set to false, then this will contain only the keys and + * values of any direct children, and not their own children. + * + * @param deep Whether or not to get a deep list, as opposed to a shallow + * list. + * @return Map of keys and values of this section. + */ + Map getValues(boolean deep); + + /** + * Checks if this {@link ConfigurationSection} contains the given path. + * + *

If the value for the requested path does not exist but a default value + * has been specified, this will return true. + * + * @param path Path to check for existence. + * @return True if this section contains the requested path, either via + * default or being set. + * @throws IllegalArgumentException Thrown when path is {@code null}. + */ + boolean contains(@Nonnull String path); + + /** + * Checks if this {@link ConfigurationSection} has a value set for the + * given path. + * + *

If the value for the requested path does not exist but a default value + * has been specified, this will still return false. + * + * @param path Path to check for existence. + * @return True if this section contains the requested path, regardless of + * having a default. + * @throws IllegalArgumentException Thrown when path is {@code null}. + */ + boolean isSet(@Nonnull String path); + + /** + * Gets the path of this {@link ConfigurationSection} from its root {@link + * Configuration}. + * + *

For any {@link Configuration} themselves, this will return an empty + * string. + * + *

If the section is no longer contained within its root for any reason, + * such as being replaced with a different value, + * this may return {@code null}. + * + *

To retrieve the single name of this section, that is, the final part + * of the path returned by this method, you may use {@link #getName()}. + * + * @return Path of this section relative to its root + */ + String getCurrentPath(); + + /** + * Gets the name of this individual {@link ConfigurationSection}, in the + * path. + * + *

This will always be the final part of {@link #getCurrentPath()}, unless + * the section is orphaned. + * + * @return Name of this section + */ + String getName(); + + /** + * Gets the root {@link Configuration} that contains this {@link + * ConfigurationSection} + * + *

For any {@link Configuration} themselves, this will return its own + * object. + * + *

If the section is no longer contained within its root for any reason, + * such as being replaced with a different value, + * this may return {@code null}. + * + * @return Root configuration containing this section. + */ + Configuration getRoot(); + + /** + * Gets the parent {@link ConfigurationSection} that directly contains + * this {@link ConfigurationSection}. + * + *

For any {@link Configuration} themselves, this will return + * {@code null}. + * + *

If the section is no longer contained within its parent for any + * reason, such as being replaced with a different value, this may + * return {@code null}. + * + * @return Parent section containing this section. + */ + ConfigurationSection getParent(); + + /** + * Gets the requested Object by path. + * + *

If the Object does not exist but a default value has been specified, + * this will return the default value. If the Object does not exist and no + * default value was specified, this will return {@code null}. + * + * @param path Path of the Object to get. + * @return Requested Object. + */ + Object get(String path); + + /** + * Gets the requested Object by path, returning a default value if not + * found. + * + *

If the Object does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the Object to get. + * @param defaultValue The default value to return if the path is not found. + * @return Requested Object. + */ + Object getOrDefault(@Nonnull String path, Object defaultValue); + + /** + * Sets the specified path to the given value. + * + *

If value is {@code null}, the entry will be removed. Any + * existing entry will be replaced, regardless of what the new value is. + * + *

Some implementations may have limitations on what you may store. See + * their individual javadoc for details. No implementations should allow + * you to store {@link Configuration}s or {@link ConfigurationSection}s, + * please use {@link #createSection(String)} for that. + * + * @param path Path of the object to set. + * @param value New value to set the path to. + */ + void set(String path, Object value); + + /** + * Creates an empty {@link ConfigurationSection} at the specified path. + * + *

Any value that was previously set at this path will be overwritten. If + * the previous value was itself a {@link ConfigurationSection}, it will + * be orphaned. + * + * @param path Path to create the section at. + * @return Newly created section + */ + ConfigurationSection createSection(String path); + + /** + * Creates a {@link ConfigurationSection} at the specified path, with + * specified values. + * + *

Any value that was previously set at this path will be overwritten. If + * the previous value was itself a {@link ConfigurationSection}, it will + * be orphaned. + * + * @param path Path to create the section at. + * @param map The values to used. + * @return Newly created section + */ + ConfigurationSection createSection(String path, Map map); + + // Primitives + + /** + * Gets the requested String by path. + * + *

If the String does not exist but a default value has been specified, + * this will return the default value. If the String does not exist and no + * default value was specified, this will return {@code null}. + * + * @param path Path of the String to get. + * @return Requested String. + */ + String getString(String path); + + /** + * Gets the requested String by path, returning a default value if not + * found. + * + *

If the String does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the String to get. + * @param def The default value to return if the path is not found or is + * not a String. + * @return Requested String. + */ + String getString(String path, String def); + + /** + * Checks if the specified path is a String. + * + *

If the path exists but is not a String, this will return false. If + * the path does not exist, this will return false. If the path does not + * exist but a default value has been specified, this will check if that + * defaultvalue is a String and return appropriately. + * + * @param path Path of the String to check. + * @return Whether or not the specified path is a String. + */ + boolean isString(String path); + + /** + * Gets the requested int by path. + * + *

If the int does not exist but a default value has been specified, this + * will return the default value. If the int does not exist and no default + * value was specified, this will return 0. + * + * @param path Path of the int to get. + * @return Requested int. + */ + int getInt(String path); + + /** + * Gets the requested int by path, returning a default value if not found. + * + *

If the int does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the int to get. + * @param def The default value to return if the path is not found or is + * not an int. + * @return Requested int. + */ + int getInt(String path, int def); + + /** + * Checks if the specified path is an int. + * + *

If the path exists but is not a int, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a int and return appropriately. + * + * @param path Path of the int to check. + * @return Whether or not the specified path is an int. + */ + boolean isInt(String path); + + /** + * Gets the requested boolean by path. + * + *

If the boolean does not exist but a default value has been specified, + * this will return the default value. If the boolean does not exist and + * no default value was specified, this will return false. + * + * @param path Path of the boolean to get. + * @return Requested boolean. + */ + boolean getBoolean(String path); + + /** + * Gets the requested boolean by path, returning a default value if not + * found. + * + *

If the boolean does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the boolean to get. + * @param defaultValue The default value to return if the path is not found or is + * not a boolean. + * @return Requested boolean. + */ + boolean getBoolean(String path, boolean defaultValue); + + /** + * Checks if the specified path is a boolean. + * + *

If the path exists but is not a boolean, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a boolean and return appropriately. + * + * @param path Path of the boolean to check. + * @return Whether or not the specified path is a boolean. + */ + boolean isBoolean(String path); + + /** + * Gets the requested double by path. + * + *

If the double does not exist but a default value has been specified, + * this will return the default value. If the double does not exist and no + * default value was specified, this will return 0. + * + * @param path Path of the double to get. + * @return Requested double. + */ + double getDouble(String path); + + /** + * Gets the requested double by path, returning a default value if not + * found. + * + *

If the double does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the double to get. + * @param defaultValue The default value to return if the path is not found or is + * not a double. + * @return Requested double. + */ + double getDouble(String path, double defaultValue); + + /** + * Checks if the specified path is a double. + * + *

If the path exists but is not a double, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a double and return appropriately. + * + * @param path Path of the double to check. + * @return Whether or not the specified path is a double. + */ + boolean isDouble(String path); + + /** + * Gets the requested long by path. + * + *

If the long does not exist but a default value has been specified, this + * will return the default value. If the long does not exist and no + * default value was specified, this will return 0. + * + * @param path Path of the long to get. + * @return Requested long. + */ + long getLong(String path); + + /** + * Gets the requested long by path, returning a default value if not + * found. + * + *

If the long does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the long to get. + * @param def The default value to return if the path is not found or is + * not a long. + * @return Requested long. + */ + long getLong(String path, long def); + + /** + * Checks if the specified path is a long. + * + *

If the path exists but is not a long, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a long and return appropriately. + * + * @param path Path of the long to check. + * @return Whether or not the specified path is a long. + */ + boolean isLong(String path); + + // Java + + /** + * Gets the requested List by path. + * + *

If the List does not exist but a default value has been specified, this + * will return the default value. If the List does not exist and no + * default value was specified, this will return null. + * + * @param path Path of the List to get. + * @return Requested List. + */ + List getList(String path); + + /** + * Gets the requested List by path, returning a default value if not + * found. + * + *

If the List does not exist then the specified default value will + * returned regardless of if a default has been identified in the root + * {@link Configuration}. + * + * @param path Path of the List to get. + * @param def The default value to return if the path is not found or is + * not a List. + * @return Requested List. + */ + List getList(String path, List def); + + /** + * Checks if the specified path is a List. + * + *

If the path exists but is not a List, this will return false. If the + * path does not exist, this will return false. If the path does not exist + * but a default value has been specified, this will check if that default + * value is a List and return appropriately. + * + * @param path Path of the List to check. + * @return Whether or not the specified path is a List. + */ + boolean isList(String path); + + /** + * Gets the requested List of String by path. + * + *

If the List does not exist but a default value has been specified, + * this will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + * + *

This method will attempt to cast any values into a String if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of String. + */ + List getStringList(String path); + + /** + * Gets the requested List of Integer by path. + * + *

If the List does not exist but a default value has been specified, + * this will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + * + *

This method will attempt to cast any values into a Integer if + * possible, but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Integer. + */ + List getIntegerList(String path); + + /** + * Gets the requested List of Boolean by path. + * + *

If the List does not exist but a default value has been specified, + * this will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + * + *

This method will attempt to cast any values into a Boolean if + * possible, but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Boolean. + */ + List getBooleanList(String path); + + /** + * Gets the requested List of Double by path. + * + *

If the List does not exist but a default value has been specified, + * this will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + * + *

This method will attempt to cast any values into a Double if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Double. + */ + List getDoubleList(String path); + + /** + * Gets the requested List of Float by path. + * + *

If the List does not exist but a default value has been specified, + * this will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + * + *

This method will attempt to cast any values into a Float if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Float. + */ + List getFloatList(String path); + + /** + * Gets the requested List of Long by path. + * + *

If the List does not exist but a default value has been specified, + * this will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + * + *

This method will attempt to cast any values into a Long if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Long. + */ + List getLongList(String path); + + /** + * Gets the requested List of Byte by path. + * + *

If the List does not exist but a default value has been specified, + * this will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + * + *

This method will attempt to cast any values into a Byte if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Byte. + */ + List getByteList(String path); + + /** + * Gets the requested List of Character by path. + * + *

If the List does not exist but a default value has been specified, + * this will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + * + *

This method will attempt to cast any values into a Character if + * possible, but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Character. + */ + List getCharacterList(String path); + + /** + * Gets the requested List of Short by path. + * + *

If the List does not exist but a default value has been specified, + * this will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + * + *

This method will attempt to cast any values into a Short if + * possible, but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Short. + */ + List getShortList(String path); + + /** + * Gets the requested List of Maps by path. + * + *

If the List does not exist but a default value has been specified, + * this will return the default value. If the List does not exist and no + * default value was specified, this will return an empty List. + *

This method will attempt to cast any values into a Map if possible, + * but may miss any values out if they are not compatible. + * + * @param path Path of the List to get. + * @return Requested List of Maps. + */ + List> getMapList(String path); + + /** + * Gets the requested ConfigurationSection by path. + * + *

If the ConfigurationSection does not exist but a default value has + * been specified, this will return the default value. If the + * ConfigurationSection does not exist and no default value was specified, + * this will return {@code null}. + * + * @param path Path of the ConfigurationSection to get. + * @return Requested ConfigurationSection. + */ + ConfigurationSection getConfigurationSection(String path); + + /** + * Checks if the specified path is a ConfigurationSection. + * + *

If the path exists but is not a ConfigurationSection, this will return + * false. If the path does not exist, this will return false. If the path + * does not exist but a default value has been specified, this will check + * if that default value is a ConfigurationSection and return + * appropriately. + * + * @param path Path of the ConfigurationSection to check. + * @return Whether or not the specified path is a ConfigurationSection. + */ + boolean isConfigurationSection(String path); + + /** + * Gets the equivalent {@link ConfigurationSection} from the default + * {@link Configuration} defined in {@link #getRoot()}. + * + *

If the root contains no defaults, or the defaults doesn't contain a + * value for this path, or the value at this path is not a {@link + * ConfigurationSection} then this will return {@code null}. + * + * @return Equivalent section in root configuration + */ + ConfigurationSection getDefaultSection(); + + /** + * Sets the default value in the root at the given path as provided. + * + *

If no source {@link Configuration} was provided as a default + * collection, then a new {@link MemoryConfiguration} will be created to + * hold the new default value. + * + *

If value is {@code null}, the value will be removed from the + * default Configuration source. + * + *

If the value as returned by {@link #getDefaultSection()} is + * {@code null}, then this will create a new section at the path, + * replacing anything that may have existed there previously. + * + * @param path Path of the value to set + * @param value Value to set the default to + * @throws IllegalArgumentException Thrown if path is {@code null} + */ + void addDefault(@Nonnull String path, Object value); +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/InvalidConfigurationException.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/InvalidConfigurationException.java new file mode 100644 index 000000000..e9c099ed1 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/InvalidConfigurationException.java @@ -0,0 +1,45 @@ +package com.github.intellectualsites.plotsquared.configuration; + +/** + * Exception thrown when attempting to load an invalid {@link Configuration}. + */ +@SuppressWarnings("serial") public class InvalidConfigurationException extends Exception { + + /** + * Creates a new instance of InvalidConfigurationException without a + * message or cause. + */ + public InvalidConfigurationException() { + } + + /** + * Constructs an instance of InvalidConfigurationException with the + * specified message. + * + * @param msg The details of the exception. + */ + public InvalidConfigurationException(String msg) { + super(msg); + } + + /** + * Constructs an instance of InvalidConfigurationException with the + * specified cause. + * + * @param cause The cause of the exception. + */ + public InvalidConfigurationException(Throwable cause) { + super(cause); + } + + /** + * Constructs an instance of InvalidConfigurationException with the + * specified message and cause. + * + * @param cause The cause of the exception. + * @param msg The details of the exception. + */ + public InvalidConfigurationException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/MemoryConfiguration.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/MemoryConfiguration.java new file mode 100644 index 000000000..d1677c54d --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/MemoryConfiguration.java @@ -0,0 +1,73 @@ +package com.github.intellectualsites.plotsquared.configuration; + +import javax.annotation.Nonnull; +import java.util.Map; + +/** + * This is a {@link Configuration} implementation that does not save or load + * from any source, and stores all values in memory only. + * This is useful for temporary Configurations for providing defaults. + */ +public class MemoryConfiguration extends MemorySection implements Configuration { + protected Configuration defaults; + protected MemoryConfigurationOptions options; + + /** + * Creates an empty {@link MemoryConfiguration} with no default values. + */ + public MemoryConfiguration() { + } + + /** + * Creates an empty {@link MemoryConfiguration} using the specified {@link + * Configuration} as a source for all default values. + * + * @param defaults Default value provider + * @throws IllegalArgumentException Thrown if defaults is null + */ + public MemoryConfiguration(Configuration defaults) { + this.defaults = defaults; + } + + @Override public void addDefault(@Nonnull String path, Object value) { + if (this.defaults == null) { + this.defaults = new MemoryConfiguration(); + } + + this.defaults.set(path, value); + } + + @Override public void addDefaults(Map defaults) { + for (Map.Entry entry : defaults.entrySet()) { + addDefault(entry.getKey(), entry.getValue()); + } + } + + @Override public void addDefaults(Configuration defaults) { + addDefaults(defaults.getValues(true)); + } + + @Override public Configuration getDefaults() { + return this.defaults; + } + + @Override public void setDefaults(Configuration defaults) { + if (defaults == null) { + throw new NullPointerException("Defaults may not be null"); + } + + this.defaults = defaults; + } + + @Override public ConfigurationSection getParent() { + return null; + } + + @Override public MemoryConfigurationOptions options() { + if (this.options == null) { + this.options = new MemoryConfigurationOptions(this); + } + + return this.options; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/MemoryConfigurationOptions.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/MemoryConfigurationOptions.java new file mode 100644 index 000000000..971ea1e70 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/MemoryConfigurationOptions.java @@ -0,0 +1,25 @@ +package com.github.intellectualsites.plotsquared.configuration; + +/** + * Various settings for controlling the input and output of a {@link + * MemoryConfiguration}. + */ +public class MemoryConfigurationOptions extends ConfigurationOptions { + protected MemoryConfigurationOptions(MemoryConfiguration configuration) { + super(configuration); + } + + @Override public MemoryConfiguration configuration() { + return (MemoryConfiguration) super.configuration(); + } + + @Override public MemoryConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @Override public MemoryConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/MemorySection.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/MemorySection.java new file mode 100644 index 000000000..26f6760c8 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/MemorySection.java @@ -0,0 +1,771 @@ +package com.github.intellectualsites.plotsquared.configuration; + +import javax.annotation.Nonnull; +import java.util.*; + +/** + * A type of {@link ConfigurationSection} that is stored in memory. + */ +public class MemorySection implements ConfigurationSection { + + protected final Map map = new LinkedHashMap<>(); + private final Configuration root; + private final ConfigurationSection parent; + private final String path; + private final String fullPath; + + /** + * Creates an empty MemorySection for use as a root {@link Configuration} section. + * + *

Note that calling this without being yourself a {@link Configuration} + * will throw an exception! + * + * @throws IllegalStateException Thrown if this is not a {@link Configuration} root. + */ + protected MemorySection() { + if (!(this instanceof Configuration)) { + throw new IllegalStateException( + "Cannot construct a root MemorySection when not a Configuration"); + } + + this.path = ""; + this.fullPath = ""; + this.parent = null; + this.root = (Configuration) this; + } + + /** + * Creates an empty MemorySection with the specified parent and path. + * + * @param parent Parent section that contains this own section. + * @param path Path that you may access this section from via the root {@link Configuration}. + * @throws IllegalArgumentException Thrown is parent or path is null, or if parent contains no + * root Configuration. + */ + protected MemorySection(ConfigurationSection parent, String path) { + this.path = path; + this.parent = parent; + this.root = parent.getRoot(); + + if (this.root == null) { + throw new NullPointerException("Path may not be orphaned"); + } + + this.fullPath = createPath(parent, path); + } + + public static double toDouble(Object obj, double def) { + if (obj instanceof Number) { + return ((Number) obj).doubleValue(); + } + if (obj instanceof String) { + try { + return Double.parseDouble((String) obj); + } catch (NumberFormatException ignored) { + } + } else if (obj instanceof List) { + List val = (List) obj; + if (!val.isEmpty()) { + return toDouble(val.get(0), def); + } + } + return def; + } + + public static int toInt(Object obj, int def) { + if (obj instanceof Number) { + return ((Number) obj).intValue(); + } + if (obj instanceof String) { + try { + return Integer.parseInt((String) obj); + } catch (NumberFormatException ignored) { + } + } else if (obj instanceof List) { + List val = (List) obj; + if (!val.isEmpty()) { + return toInt(val.get(0), def); + } + } + return def; + } + + public static long toLong(Object obj, long def) { + if (obj instanceof Number) { + return ((Number) obj).longValue(); + } + if (obj instanceof String) { + try { + return Long.parseLong((String) obj); + } catch (NumberFormatException ignored) { + } + } else if (obj instanceof List) { + List val = (List) obj; + if (!val.isEmpty()) { + return toLong(val.get(0), def); + } + } + return def; + } + + /** + * Creates a full path to the given {@link ConfigurationSection} from its root {@link + * Configuration}. + * + *

You may use this method for any given {@link ConfigurationSection}, not + * only {@link MemorySection}. + * + * @param section Section to create a path for. + * @param key Name of the specified section. + * @return Full path of the section from its root. + */ + public static String createPath(ConfigurationSection section, String key) { + return createPath(section, key, section.getRoot()); + } + + /** + * Creates a relative path to the given {@link ConfigurationSection} from the given relative + * section. + * + *

You may use this method for any given {@link ConfigurationSection}, not + * only {@link MemorySection}. + * + * @param section Section to create a path for. + * @param key Name of the specified section. + * @param relativeTo Section to create the path relative to. + * @return Full path of the section from its root. + */ + public static String createPath(ConfigurationSection section, String key, + ConfigurationSection relativeTo) { + Configuration root = section.getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot create path without a root"); + } + char separator = root.options().pathSeparator(); + + StringBuilder builder = new StringBuilder(); + for (ConfigurationSection parent = section; + (parent != null) && (parent != relativeTo); parent = parent.getParent()) { + if (builder.length() > 0) { + builder.insert(0, separator); + } + + builder.insert(0, parent.getName()); + } + + if ((key != null) && !key.isEmpty()) { + if (builder.length() > 0) { + builder.append(separator); + } + + builder.append(key); + } + + return builder.toString(); + } + + @Override public Set getKeys(boolean deep) { + Set result = new LinkedHashSet<>(); + + Configuration root = getRoot(); + if ((root != null) && root.options().copyDefaults()) { + ConfigurationSection defaults = getDefaultSection(); + + if (defaults != null) { + result.addAll(defaults.getKeys(deep)); + } + } + + mapChildrenKeys(result, this, deep); + + return result; + } + + @Override public Map getValues(boolean deep) { + Map result = new LinkedHashMap<>(); + + Configuration root = getRoot(); + if ((root != null) && root.options().copyDefaults()) { + ConfigurationSection defaults = getDefaultSection(); + + if (defaults != null) { + result.putAll(defaults.getValues(deep)); + } + } + + mapChildrenValues(result, this, deep); + + return result; + } + + @Override public boolean contains(@Nonnull String path) { + return get(path) != null; + } + + @Override public boolean isSet(@Nonnull String path) { + Configuration root = getRoot(); + if (root == null) { + return false; + } + if (root.options().copyDefaults()) { + return contains(path); + } + return getOrDefault(path, null) != null; + } + + @Override public String getCurrentPath() { + return this.fullPath; + } + + @Override public String getName() { + return this.path; + } + + @Override public Configuration getRoot() { + return this.root; + } + + @Override public ConfigurationSection getParent() { + return this.parent; + } + + @Override public void addDefault(@Nonnull String path, Object value) { + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot add default without root"); + } + if (root == this) { + throw new UnsupportedOperationException( + "Unsupported addDefault(String, Object) implementation"); + } + root.addDefault(createPath(this, path), value); + } + + @Override public ConfigurationSection getDefaultSection() { + Configuration root = getRoot(); + Configuration defaults = root == null ? null : root.getDefaults(); + + if (defaults != null) { + if (defaults.isConfigurationSection(getCurrentPath())) { + return defaults.getConfigurationSection(getCurrentPath()); + } + } + + return null; + } + + @Override public void set(String path, Object value) { + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot use section without a root"); + } + + char separator = root.options().pathSeparator(); + // i1 is the leading (higher) index + // i2 is the trailing (lower) index + int i1 = -1; + int i2; + ConfigurationSection section = this; + while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { + String node = path.substring(i2, i1); + ConfigurationSection subSection = section.getConfigurationSection(node); + if (subSection == null) { + section = section.createSection(node); + } else { + section = subSection; + } + } + + String key = path.substring(i2); + if (section == this) { + if (value == null) { + this.map.remove(key); + } else { + this.map.put(key, value); + } + } else { + section.set(key, value); + } + } + + @Override public Object get(String path) { + return getOrDefault(path, getDefault(path)); + } + + @Override public Object getOrDefault(@Nonnull String path, Object defaultValue) { + if (path.isEmpty()) { + return this; + } + + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot access section without a root"); + } + + char separator = root.options().pathSeparator(); + // i1 is the leading (higher) index + // i2 is the trailing (lower) index + int i1 = -1; + int i2; + ConfigurationSection section = this; + while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { + section = section.getConfigurationSection(path.substring(i2, i1)); + if (section == null) { + return defaultValue; + } + } + + String key = path.substring(i2); + if (section == this) { + Object result = this.map.get(key); + if (result == null) { + return defaultValue; + } else { + return result; + } + } + return section.getOrDefault(key, defaultValue); + } + + @Override public ConfigurationSection createSection(String path) { + Configuration root = getRoot(); + if (root == null) { + throw new IllegalStateException("Cannot create section without a root"); + } + + char separator = root.options().pathSeparator(); + // i1 is the leading (higher) index + // i2 is the trailing (lower) index + int i1 = -1; + int i2; + ConfigurationSection section = this; + while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { + String node = path.substring(i2, i1); + ConfigurationSection subSection = section.getConfigurationSection(node); + if (subSection == null) { + section = section.createSection(node); + } else { + section = subSection; + } + } + + String key = path.substring(i2); + if (section == this) { + ConfigurationSection result = new MemorySection(this, key); + this.map.put(key, result); + return result; + } + return section.createSection(key); + } + + @Override public ConfigurationSection createSection(String path, Map map) { + ConfigurationSection section = createSection(path); + + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() instanceof Map) { + section.createSection(entry.getKey().toString(), (Map) entry.getValue()); + } else { + section.set(entry.getKey().toString(), entry.getValue()); + } + } + + return section; + } + + // Primitives + @Override public String getString(String path) { + Object def = getDefault(path); + return getString(path, def != null ? def.toString() : null); + } + + @Override public String getString(String path, String def) { + Object val = getOrDefault(path, def); + if (val != null) { + return val.toString(); + } else { + return def; + } + } + + @Override public boolean isString(String path) { + Object val = get(path); + return val instanceof String; + } + + @Override public int getInt(String path) { + Object def = getDefault(path); + return getInt(path, toInt(def, 0)); + } + + @Override public int getInt(String path, int def) { + Object val = getOrDefault(path, def); + return toInt(val, def); + } + + @Override public boolean isInt(String path) { + Object val = get(path); + return val instanceof Integer; + } + + @Override public boolean getBoolean(String path) { + Object def = getDefault(path); + if (def instanceof Boolean) { + return getBoolean(path, (Boolean) def); + } else { + return getBoolean(path, false); + } + } + + @Override public boolean getBoolean(String path, boolean defaultValue) { + Object val = getOrDefault(path, defaultValue); + if (val instanceof Boolean) { + return (Boolean) val; + } else { + return defaultValue; + } + } + + @Override public boolean isBoolean(String path) { + Object val = get(path); + return val instanceof Boolean; + } + + @Override public double getDouble(String path) { + Object def = getDefault(path); + return getDouble(path, toDouble(def, 0)); + } + + @Override public double getDouble(String path, double defaultValue) { + Object val = getOrDefault(path, defaultValue); + return toDouble(val, defaultValue); + } + + @Override public boolean isDouble(String path) { + Object val = get(path); + return val instanceof Double; + } + + @Override public long getLong(String path) { + Object def = getDefault(path); + return getLong(path, toLong(def, 0)); + } + + @Override public long getLong(String path, long def) { + Object val = getOrDefault(path, def); + return toLong(val, def); + } + + @Override public boolean isLong(String path) { + Object val = get(path); + return val instanceof Long; + } + + // Java + @Override public List getList(String path) { + Object def = getDefault(path); + return getList(path, def instanceof List ? (List) def : null); + } + + @Override public List getList(String path, List def) { + Object val = getOrDefault(path, def); + return (List) ((val instanceof List) ? val : def); + } + + @Override public boolean isList(String path) { + Object val = get(path); + return val instanceof List; + } + + @Override public List getStringList(String path) { + List list = getList(path); + + if (list == null) { + return new ArrayList<>(0); + } + + List result = new ArrayList<>(); + + for (Object object : list) { + if ((object instanceof String) || isPrimitiveWrapper(object)) { + result.add(String.valueOf(object)); + } + } + + return result; + } + + @Override public List getIntegerList(String path) { + List list = getList(path); + + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Integer) { + result.add((Integer) object); + } else if (object instanceof String) { + try { + result.add(Integer.valueOf((String) object)); + } catch (NumberFormatException ignored) { + } + } else if (object instanceof Character) { + result.add((int) (Character) object); + } else if (object instanceof Number) { + result.add(((Number) object).intValue()); + } + } + + return result; + } + + @Override public List getBooleanList(String path) { + List list = getList(path); + + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Boolean) { + result.add((Boolean) object); + } else if (object instanceof String) { + if (Boolean.TRUE.toString().equals(object)) { + result.add(true); + } else if (Boolean.FALSE.toString().equals(object)) { + result.add(false); + } + } + } + + return result; + } + + @Override public List getDoubleList(String path) { + List list = getList(path); + + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Double) { + result.add((Double) object); + } else if (object instanceof String) { + try { + result.add(Double.valueOf((String) object)); + } catch (NumberFormatException ignored) { + } + } else if (object instanceof Character) { + result.add((double) (Character) object); + } else if (object instanceof Number) { + result.add(((Number) object).doubleValue()); + } + } + + return result; + } + + @Override public List getFloatList(String path) { + List list = getList(path); + + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Float) { + result.add((Float) object); + } else if (object instanceof String) { + try { + result.add(Float.valueOf((String) object)); + } catch (NumberFormatException ignored) { + } + } else if (object instanceof Character) { + result.add((float) (Character) object); + } else if (object instanceof Number) { + result.add(((Number) object).floatValue()); + } + } + + return result; + } + + @Override public List getLongList(String path) { + List list = getList(path); + + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Long) { + result.add((Long) object); + } else if (object instanceof String) { + try { + result.add(Long.valueOf((String) object)); + } catch (NumberFormatException ignored) { + } + } else if (object instanceof Character) { + result.add((long) (Character) object); + } else if (object instanceof Number) { + result.add(((Number) object).longValue()); + } + } + + return result; + } + + @Override public List getByteList(String path) { + List list = getList(path); + + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Byte) { + result.add((Byte) object); + } else if (object instanceof String) { + try { + result.add(Byte.valueOf((String) object)); + } catch (NumberFormatException ignored) { + } + } else if (object instanceof Character) { + result.add((byte) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).byteValue()); + } + } + + return result; + } + + @Override public List getCharacterList(String path) { + List list = getList(path); + + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Character) { + result.add((Character) object); + } else if (object instanceof String) { + String str = (String) object; + + if (str.length() == 1) { + result.add(str.charAt(0)); + } + } else if (object instanceof Number) { + result.add((char) ((Number) object).intValue()); + } + } + + return result; + } + + @Override public List getShortList(String path) { + List list = getList(path); + + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Short) { + result.add((Short) object); + } else if (object instanceof String) { + try { + result.add(Short.valueOf((String) object)); + } catch (NumberFormatException ignored) { + } + } else if (object instanceof Character) { + result.add((short) ((Character) object).charValue()); + } else if (object instanceof Number) { + result.add(((Number) object).shortValue()); + } + } + + return result; + } + + @Override public List> getMapList(String path) { + List list = getList(path); + List> result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Map) { + result.add((Map) object); + } + } + + return result; + } + + @Override public ConfigurationSection getConfigurationSection(String path) { + Object val = getOrDefault(path, null); + if (val != null) { + return (val instanceof ConfigurationSection) ? (ConfigurationSection) val : null; + } + + val = getOrDefault(path, getDefault(path)); + return (val instanceof ConfigurationSection) ? createSection(path) : null; + } + + @Override public boolean isConfigurationSection(String path) { + Object val = get(path); + return val instanceof ConfigurationSection; + } + + protected boolean isPrimitiveWrapper(Object input) { + return (input instanceof Integer) || (input instanceof Boolean) + || (input instanceof Character) || (input instanceof Byte) || (input instanceof Short) + || (input instanceof Double) || (input instanceof Long) || (input instanceof Float); + } + + protected Object getDefault(String path) { + Configuration root = getRoot(); + Configuration defaults = root == null ? null : root.getDefaults(); + return (defaults == null) ? null : defaults.get(createPath(this, path)); + } + + protected void mapChildrenKeys(Set output, ConfigurationSection section, boolean deep) { + if (section instanceof MemorySection) { + MemorySection sec = (MemorySection) section; + + for (Map.Entry entry : sec.map.entrySet()) { + output.add(createPath(section, entry.getKey(), this)); + + if (deep && (entry.getValue() instanceof ConfigurationSection)) { + ConfigurationSection subsection = (ConfigurationSection) entry.getValue(); + mapChildrenKeys(output, subsection, deep); + } + } + } else { + Set keys = section.getKeys(deep); + + for (String key : keys) { + output.add(createPath(section, key, this)); + } + } + } + + protected void mapChildrenValues(Map output, ConfigurationSection section, + boolean deep) { + if (section instanceof MemorySection) { + MemorySection sec = (MemorySection) section; + + for (Map.Entry entry : sec.map.entrySet()) { + output.put(createPath(section, entry.getKey(), this), entry.getValue()); + + if (entry.getValue() instanceof ConfigurationSection) { + if (deep) { + mapChildrenValues(output, (ConfigurationSection) entry.getValue(), deep); + } + } + } + } else { + Map values = section.getValues(deep); + + for (Map.Entry entry : values.entrySet()) { + output.put(createPath(section, entry.getKey(), this), entry.getValue()); + } + } + } + + @Override public String toString() { + Configuration root = getRoot(); + if (root == null) { + return getClass().getSimpleName() + "[path='" + getCurrentPath() + "', root='" + null + + "']"; + } else { + return getClass().getSimpleName() + "[path='" + getCurrentPath() + "', root='" + root + .getClass().getSimpleName() + "']"; + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/FileConfiguration.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/FileConfiguration.java new file mode 100644 index 000000000..bc0b375e9 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/FileConfiguration.java @@ -0,0 +1,158 @@ +package com.github.intellectualsites.plotsquared.configuration.file; + +import com.github.intellectualsites.plotsquared.configuration.Configuration; +import com.github.intellectualsites.plotsquared.configuration.InvalidConfigurationException; +import com.github.intellectualsites.plotsquared.configuration.MemoryConfiguration; + +import javax.annotation.Nonnull; +import java.io.*; +import java.nio.charset.StandardCharsets; + +/** + * This is a base class for all File based implementations of {@link + * Configuration}. + */ +public abstract class FileConfiguration extends MemoryConfiguration { + + /** + * Creates an empty {@link FileConfiguration} with no default values. + */ + FileConfiguration() { + } + + /** + * Creates an empty {@link FileConfiguration} using the specified {@link + * Configuration} as a source for all default values. + * + * @param defaults Default value provider + */ + public FileConfiguration(Configuration defaults) { + super(defaults); + } + + /** + * Saves this {@link FileConfiguration} to the specified location. + * + *

If the file does not exist, it will be created. If already exists, it + * will be overwritten. If it cannot be overwritten or created, an + * exception will be thrown. + * + *

This method will save using the system default encoding, or possibly + * using UTF8. + * + * @param file File to save to. + * @throws IOException Thrown when the given file cannot be written to for + * any reason. + */ + public void save(File file) throws IOException { + File parent = file.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + + String data = saveToString(); + + try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), + StandardCharsets.UTF_8)) { + writer.write(data); + } + } + + /** + * Saves this {@link FileConfiguration} to a string, and returns it. + * + * @return String containing this configuration. + */ + public abstract String saveToString(); + + /** + * Loads this {@link FileConfiguration} from the specified location. + * + *

All the values contained within this configuration will be removed, + * leaving only settings and defaults, and the new values will be loaded + * from the given file. + * + *

If the file cannot be loaded for any reason, an exception will be + * thrown. + * + * @param file File to load from. + * @throws FileNotFoundException Thrown when the given file cannot be + * opened. + * @throws IOException Thrown when the given file cannot be read. + * @throws InvalidConfigurationException Thrown when the given file is not + * a valid Configuration. + * @throws IllegalArgumentException Thrown when file is null. + */ + public void load(@Nonnull File file) throws IOException, InvalidConfigurationException { + + FileInputStream stream = new FileInputStream(file); + + load(new InputStreamReader(stream, StandardCharsets.UTF_8)); + } + + /** + * Loads this {@link FileConfiguration} from the specified reader. + * + *

All the values contained within this configuration will be removed, + * leaving only settings and defaults, and the new values will be loaded + * from the given stream. + * + * @param reader the reader to load from + * @throws IOException thrown when underlying reader throws an IOException + * @throws InvalidConfigurationException thrown when the reader does not + * represent a valid Configuration + */ + public void load(Reader reader) throws IOException, InvalidConfigurationException { + + StringBuilder builder = new StringBuilder(); + + try (BufferedReader input = reader instanceof BufferedReader ? + (BufferedReader) reader : + new BufferedReader(reader)) { + String line; + + while ((line = input.readLine()) != null) { + builder.append(line); + builder.append('\n'); + } + } + + loadFromString(builder.toString()); + } + + /** + * Loads this {@link FileConfiguration} from the specified string, as + * opposed to from file. + * + *

All the values contained within this configuration will be removed, + * leaving only settings and defaults, and the new values will be loaded + * from the given string. + * + *

If the string is invalid in any way, an exception will be thrown. + * + * @param contents Contents of a Configuration to load. + * @throws InvalidConfigurationException Thrown if the specified string is + * invalid. + */ + public abstract void loadFromString(String contents) throws InvalidConfigurationException; + + /** + * Compiles the header for this {@link FileConfiguration} and returns the + * result. + * + *

This will use the header from {@link #options()} -> {@link + * FileConfigurationOptions#header()}, respecting the rules of {@link + * FileConfigurationOptions#copyHeader()} if set. + * + * @return Compiled header + */ + protected abstract String buildHeader(); + + @Override public FileConfigurationOptions options() { + if (this.options == null) { + this.options = new FileConfigurationOptions(this); + } + + return (FileConfigurationOptions) this.options; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/FileConfigurationOptions.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/FileConfigurationOptions.java new file mode 100644 index 000000000..a216d0566 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/FileConfigurationOptions.java @@ -0,0 +1,115 @@ +package com.github.intellectualsites.plotsquared.configuration.file; + +import com.github.intellectualsites.plotsquared.configuration.Configuration; +import com.github.intellectualsites.plotsquared.configuration.MemoryConfiguration; +import com.github.intellectualsites.plotsquared.configuration.MemoryConfigurationOptions; + +/** + * Various settings for controlling the input and output of a {@link + * FileConfiguration}. + */ +public class FileConfigurationOptions extends MemoryConfigurationOptions { + private String header = null; + private boolean copyHeader = true; + + protected FileConfigurationOptions(MemoryConfiguration configuration) { + super(configuration); + } + + @Override public FileConfiguration configuration() { + return (FileConfiguration) super.configuration(); + } + + @Override public FileConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @Override public FileConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } + + /** + * Gets the header that will be applied to the top of the saved output. + * + *

This header will be commented out and applied directly at the top of + * the generated output of the {@link FileConfiguration}. It is not + * required to include a newline at the end of the header as it will + * automatically be applied, but you may include one if you wish for extra + * spacing. + * + *

{@code null} is a valid value which will indicate that no header] + * is to be applied. The default value is {@code null}. + * + * @return Header + */ + public String header() { + return header; + } + + /** + * Sets the header that will be applied to the top of the saved output. + * + *

This header will be commented out and applied directly at the top of + * the generated output of the {@link FileConfiguration}. It is not + * required to include a newline at the end of the header as it will + * automatically be applied, but you may include one if you wish for extra + * spacing. + * + *

{@code null} is a valid value which will indicate that no header + * is to be applied. + * + * @param value New header + * @return This object, for chaining + */ + public FileConfigurationOptions header(String value) { + header = value; + return this; + } + + /** + * Gets whether or not the header should be copied from a default source. + * + *

If this is true, if a default {@link FileConfiguration} is passed to + * {@link FileConfiguration#setDefaults(Configuration)} + * then upon saving it will use the header from that config, instead of + * the one provided here. + * + *

If no default is set on the configuration, or the default is not of + * type FileConfiguration, or that config has no header ({@link #header()} + * returns null) then the header specified in this configuration will be + * used. + * + *

Defaults to true. + * + * @return Whether or not to copy the header + */ + public boolean copyHeader() { + return copyHeader; + } + + /** + * Sets whether or not the header should be copied from a default source. + * + *

If this is true, if a default {@link FileConfiguration} is passed to + * {@link FileConfiguration#setDefaults(Configuration)} + * then upon saving it will use the header from that config, instead of + * the one provided here. + * + *

If no default is set on the configuration, or the default is not of + * type FileConfiguration, or that config has no header ({@link #header()} + * returns null) then the header specified in this configuration will be + * used. + * + *

Defaults to true. + * + * @param value Whether or not to copy the header + * @return This object, for chaining + */ + public FileConfigurationOptions copyHeader(boolean value) { + copyHeader = value; + + return this; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlConfiguration.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlConfiguration.java new file mode 100644 index 000000000..1e694f05a --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlConfiguration.java @@ -0,0 +1,189 @@ +package com.github.intellectualsites.plotsquared.configuration.file; + +import com.github.intellectualsites.plotsquared.configuration.Configuration; +import com.github.intellectualsites.plotsquared.configuration.ConfigurationSection; +import com.github.intellectualsites.plotsquared.configuration.InvalidConfigurationException; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.error.YAMLException; +import org.yaml.snakeyaml.representer.Representer; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Map; + +/** + * An implementation of {@link Configuration} which saves all files in Yaml. + * Note that this implementation is not synchronized. + */ +public class YamlConfiguration extends FileConfiguration { + private static final String COMMENT_PREFIX = "# "; + private static final String BLANK_CONFIG = "{}\n"; + private final DumperOptions yamlOptions = new DumperOptions(); + private final Representer yamlRepresenter = new YamlRepresenter(); + private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions); + + /** + * Creates a new {@link YamlConfiguration}, loading from the given file. + * + *

Any errors loading the Configuration will be logged and then ignored. + * If the specified input is not a valid config, a blank config will be + * returned. + * + *

The encoding used may follow the system dependent default. + * + * @param file Input file + * @return Resulting configuration + */ + public static YamlConfiguration loadConfiguration(File file) { + YamlConfiguration config = new YamlConfiguration(); + + try { + config.load(file); + } catch (InvalidConfigurationException | IOException ex) { + try { + File dest = new File(file.getAbsolutePath() + "_broken"); + int i = 0; + while (dest.exists()) { + dest = new File(file.getAbsolutePath() + "_broken_" + i++); + } + Files.copy(file.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING); + PlotSquared.debug("&dCould not read: &7" + file); + PlotSquared.debug("&dRenamed to: &7" + dest.getName()); + PlotSquared.debug("&c============ Full stacktrace ============"); + ex.printStackTrace(); + PlotSquared.debug("&c========================================="); + } catch (IOException e) { + e.printStackTrace(); + } + } + + return config; + } + + @Override public String saveToString() { + yamlOptions.setIndent(options().indent()); + yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + + String header = buildHeader(); + String dump = yaml.dump(getValues(false)); + + if (dump.equals(BLANK_CONFIG)) { + dump = ""; + } + + return header + dump; + } + + @Override public void loadFromString(String contents) throws InvalidConfigurationException { + + Map input; + try { + input = yaml.load(contents); + } catch (YAMLException e) { + throw new InvalidConfigurationException(e); + } catch (ClassCastException ignored) { + throw new InvalidConfigurationException("Top level is not a Map."); + } + + String header = parseHeader(contents); + if (!header.isEmpty()) { + options().header(header); + } + + if (input != null) { + convertMapsToSections(input, this); + } + } + + protected void convertMapsToSections(Map input, ConfigurationSection section) { + for (Map.Entry entry : input.entrySet()) { + String key = entry.getKey().toString(); + Object value = entry.getValue(); + + if (value instanceof Map) { + convertMapsToSections((Map) value, section.createSection(key)); + } else { + section.set(key, value); + } + } + } + + protected String parseHeader(String input) { + String[] lines = input.split("\r?\n", -1); + StringBuilder result = new StringBuilder(); + boolean readingHeader = true; + boolean foundHeader = false; + + for (int i = 0; (i < lines.length) && readingHeader; i++) { + String line = lines[i]; + + if (line.startsWith(COMMENT_PREFIX)) { + if (i > 0) { + result.append('\n'); + } + + if (line.length() > COMMENT_PREFIX.length()) { + result.append(line.substring(COMMENT_PREFIX.length())); + } + + foundHeader = true; + } else if (foundHeader && line.isEmpty()) { + result.append('\n'); + } else if (foundHeader) { + readingHeader = false; + } + } + + return result.toString(); + } + + @Override protected String buildHeader() { + String header = options().header(); + + if (options().copyHeader()) { + Configuration def = getDefaults(); + + if (def instanceof FileConfiguration) { + FileConfiguration fileDefaults = (FileConfiguration) def; + String defaultsHeader = fileDefaults.buildHeader(); + + if ((defaultsHeader != null) && !defaultsHeader.isEmpty()) { + return defaultsHeader; + } + } + } + + if (header == null) { + return ""; + } + + StringBuilder builder = new StringBuilder(); + String[] lines = header.split("\r?\n", -1); + boolean startedHeader = false; + + for (int i = lines.length - 1; i >= 0; i--) { + builder.insert(0, '\n'); + + if (startedHeader || !lines[i].isEmpty()) { + builder.insert(0, lines[i]); + builder.insert(0, COMMENT_PREFIX); + startedHeader = true; + } + } + + return builder.toString(); + } + + @Override public YamlConfigurationOptions options() { + if (options == null) { + options = new YamlConfigurationOptions(this); + } + + return (YamlConfigurationOptions) options; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlConfigurationOptions.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlConfigurationOptions.java new file mode 100644 index 000000000..730527652 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlConfigurationOptions.java @@ -0,0 +1,68 @@ +package com.github.intellectualsites.plotsquared.configuration.file; + +/** + * Various settings for controlling the input and output of a {@link + * YamlConfiguration}. + */ +public class YamlConfigurationOptions extends FileConfigurationOptions { + private int indent = 2; + + YamlConfigurationOptions(YamlConfiguration configuration) { + super(configuration); + } + + @Override public YamlConfiguration configuration() { + return (YamlConfiguration) super.configuration(); + } + + @Override public YamlConfigurationOptions copyDefaults(boolean value) { + super.copyDefaults(value); + return this; + } + + @Override public YamlConfigurationOptions pathSeparator(char value) { + super.pathSeparator(value); + return this; + } + + @Override public YamlConfigurationOptions header(String value) { + super.header(value); + return this; + } + + @Override public YamlConfigurationOptions copyHeader(boolean value) { + super.copyHeader(value); + return this; + } + + /** + * Gets how much spaces should be used to indent each line. + * + *

The minimum value this may be is 2, and the maximum is 9. + * + * @return How much to indent by + */ + int indent() { + return indent; + } + + /** + * Sets how much spaces should be used to indent each line. + * + *

The minimum value this may be is 2, and the maximum is 9. + * + * @param value New indent + * @return This object, for chaining + */ + public YamlConfigurationOptions indent(int value) { + if (value < 2) { + throw new IllegalArgumentException("Indent must be at least 2 characters"); + } + if (value > 9) { + throw new IllegalArgumentException("Indent cannot be greater than 9 characters"); + } + + indent = value; + return this; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlConstructor.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlConstructor.java new file mode 100644 index 000000000..cdc5eb821 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlConstructor.java @@ -0,0 +1,46 @@ +package com.github.intellectualsites.plotsquared.configuration.file; + +import com.github.intellectualsites.plotsquared.configuration.serialization.ConfigurationSerialization; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.error.YAMLException; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.Tag; + +import java.util.LinkedHashMap; +import java.util.Map; + +class YamlConstructor extends SafeConstructor { + + YamlConstructor() { + yamlConstructors.put(Tag.MAP, new ConstructCustomObject()); + } + + private class ConstructCustomObject extends ConstructYamlMap { + @Override public Object construct(final Node node) { + if (node.isTwoStepsConstruction()) { + throw new YAMLException("Unexpected referential mapping structure. Node: " + node); + } + + final Map raw = (Map) super.construct(node); + + if (raw.containsKey(ConfigurationSerialization.SERIALIZED_TYPE_KEY)) { + final Map typed = new LinkedHashMap<>(raw.size()); + for (final Map.Entry entry : raw.entrySet()) { + typed.put(entry.getKey().toString(), entry.getValue()); + } + + try { + return ConfigurationSerialization.deserializeObject(typed); + } catch (final IllegalArgumentException ex) { + throw new YAMLException("Could not deserialize object", ex); + } + } + + return raw; + } + + @Override public void construct2ndStep(final Node node, final Object object) { + throw new YAMLException("Unexpected referential mapping structure. Node: " + node); + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlRepresenter.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlRepresenter.java new file mode 100644 index 000000000..ebe1aebee --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/file/YamlRepresenter.java @@ -0,0 +1,40 @@ +package com.github.intellectualsites.plotsquared.configuration.file; + +import com.github.intellectualsites.plotsquared.configuration.ConfigurationSection; +import com.github.intellectualsites.plotsquared.configuration.serialization.ConfigurationSerializable; +import com.github.intellectualsites.plotsquared.configuration.serialization.ConfigurationSerialization; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.representer.Representer; + +import java.util.LinkedHashMap; +import java.util.Map; + +class YamlRepresenter extends Representer { + + YamlRepresenter() { + this.multiRepresenters.put(ConfigurationSection.class, new RepresentConfigurationSection()); + this.multiRepresenters + .put(ConfigurationSerializable.class, new RepresentConfigurationSerializable()); + } + + private class RepresentConfigurationSection extends RepresentMap { + + @Override public Node representData(Object data) { + return super.representData(((ConfigurationSection) data).getValues(false)); + } + } + + + private class RepresentConfigurationSerializable extends RepresentMap { + + @Override public Node representData(Object data) { + ConfigurationSerializable serializable = (ConfigurationSerializable) data; + Map values = new LinkedHashMap<>(); + values.put(ConfigurationSerialization.SERIALIZED_TYPE_KEY, + ConfigurationSerialization.getAlias(serializable.getClass())); + values.putAll(serializable.serialize()); + + return super.representData(values); + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/ConfigurationSerializable.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/ConfigurationSerializable.java new file mode 100644 index 000000000..f83e7a468 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/ConfigurationSerializable.java @@ -0,0 +1,34 @@ +package com.github.intellectualsites.plotsquared.configuration.serialization; + +import java.util.Map; + +/** + * Represents an object that may be serialized. + *

These objects MUST implement one of the following, in addition to + * the methods as defined by this interface: + *

    + *
  • A static method "deserialize" that accepts a single {@link Map}< + * {@link String}, {@link Object}> and returns the class.
  • + *
  • A static method "valueOf" that accepts a single {@link Map}<{@link + * String}, {@link Object}> and returns the class.
  • + *
  • A constructor that accepts a single {@link Map}<{@link String}, + * {@link Object}>.
  • + *
+ * In addition to implementing this interface, you must register the class + * with {@link ConfigurationSerialization#registerClass(Class)}. + * + * @see DelegateDeserialization + * @see SerializableAs + */ +public interface ConfigurationSerializable { + + /** + * Creates a Map representation of this class. + * + *

This class must provide a method to restore this class, as defined in + * the {@link ConfigurationSerializable} interface javadoc. + * + * @return Map containing the current state of this class + */ + Map serialize(); +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/ConfigurationSerialization.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/ConfigurationSerialization.java new file mode 100644 index 000000000..0783b4af5 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/ConfigurationSerialization.java @@ -0,0 +1,262 @@ +package com.github.intellectualsites.plotsquared.configuration.serialization; + +import com.github.intellectualsites.plotsquared.configuration.Configuration; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Utility class for storing and retrieving classes for {@link Configuration}. + */ +public class ConfigurationSerialization { + + public static final String SERIALIZED_TYPE_KEY = "=="; + private static final Map> aliases = + new HashMap<>(); + private final Class clazz; + + protected ConfigurationSerialization(Class clazz) { + this.clazz = clazz; + } + + /** + * Attempts to deserialize the given arguments into a new instance of the + * given class. + *

+ *

The class must implement {@link ConfigurationSerializable}, including + * the extra methods as specified in the javadoc of + * ConfigurationSerializable.

+ *

+ *

If a new instance could not be made, an example being the class not + * fully implementing the interface, null will be returned.

+ * + * @param args Arguments for deserialization + * @param clazz Class to deserialize into + * @return New instance of the specified class + */ + public static ConfigurationSerializable deserializeObject(Map args, + Class clazz) { + return new ConfigurationSerialization(clazz).deserialize(args); + } + + /** + * Attempts to deserialize the given arguments into a new instance of the + *

+ * given class. + *

+ * The class must implement {@link ConfigurationSerializable}, including + * the extra methods as specified in the javadoc of + * ConfigurationSerializable.

+ *

+ *

+ * If a new instance could not be made, an example being the class not + * fully implementing the interface, null will be returned.

+ * + * @param args Arguments for deserialization + * @return New instance of the specified class + */ + public static ConfigurationSerializable deserializeObject(Map args) { + Class clazz = null; + + if (args.containsKey(SERIALIZED_TYPE_KEY)) { + try { + String alias = (String) args.get(SERIALIZED_TYPE_KEY); + + if (alias == null) { + throw new IllegalArgumentException("Cannot have null alias"); + } + clazz = getClassByAlias(alias); + if (clazz == null) { + throw new IllegalArgumentException( + "Specified class does not exist ('" + alias + "')"); + } + } catch (ClassCastException ex) { + ex.fillInStackTrace(); + throw ex; + } + } else { + throw new IllegalArgumentException( + "Args doesn't contain type key ('" + SERIALIZED_TYPE_KEY + "')"); + } + + return new ConfigurationSerialization(clazz).deserialize(args); + } + + /** + * Registers the given {@link ConfigurationSerializable} class by its + * alias. + * + * @param clazz Class to register + */ + public static void registerClass(Class clazz) { + DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class); + + if (delegate == null) { + registerClass(clazz, getAlias(clazz)); + registerClass(clazz, clazz.getName()); + } + } + + /** + * Registers the given alias to the specified {@link + * ConfigurationSerializable} class. + * + * @param clazz Class to register + * @param alias Alias to register as + * @see SerializableAs + */ + public static void registerClass(Class clazz, + String alias) { + aliases.put(alias, clazz); + } + + /** + * Unregisters the specified alias to a {@link ConfigurationSerializable} + * + * @param alias Alias to unregister + */ + public static void unregisterClass(String alias) { + aliases.remove(alias); + } + + /** + * Unregisters any aliases for the specified {@link + * ConfigurationSerializable} class. + * + * @param clazz Class to unregister + */ + public static void unregisterClass(Class clazz) { + while (aliases.values().remove(clazz)) { + } + } + + /** + * Attempts to get a registered {@link ConfigurationSerializable} class by + * its alias. + * + * @param alias Alias of the serializable + * @return Registered class, or null if not found + */ + public static Class getClassByAlias(String alias) { + return aliases.get(alias); + } + + /** + * Gets the correct alias for the given {@link ConfigurationSerializable} + * class. + * + * @param clazz Class to get alias for + * @return Alias to use for the class + */ + public static String getAlias(Class clazz) { + DelegateDeserialization delegate = clazz.getAnnotation(DelegateDeserialization.class); + + if (delegate != null) { + if (delegate.value() == clazz) { + delegate = null; + } else { + return getAlias(delegate.value()); + } + } + + SerializableAs alias = clazz.getAnnotation(SerializableAs.class); + + if (alias != null) { + return alias.value(); + } + + return clazz.getName(); + } + + protected Method getMethod(String name, boolean isStatic) { + try { + Method method = this.clazz.getDeclaredMethod(name, Map.class); + + if (!ConfigurationSerializable.class.isAssignableFrom(method.getReturnType())) { + return null; + } + if (Modifier.isStatic(method.getModifiers()) != isStatic) { + return null; + } + + return method; + } catch (NoSuchMethodException | SecurityException ignored) { + return null; + } + } + + protected Constructor getConstructor() { + try { + return this.clazz.getConstructor(Map.class); + } catch (NoSuchMethodException | SecurityException ignored) { + return null; + } + } + + protected ConfigurationSerializable deserializeViaMethod(Method method, Map args) { + try { + ConfigurationSerializable result = + (ConfigurationSerializable) method.invoke(null, args); + + if (result == null) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, + "Could not call method '" + method.toString() + "' of " + this.clazz + + " for deserialization: method returned null"); + } else { + return result; + } + } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException ex) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, + "Could not call method '" + method.toString() + "' of " + this.clazz + + " for deserialization", + ex instanceof InvocationTargetException ? ex.getCause() : ex); + } + + return null; + } + + protected ConfigurationSerializable deserializeViaCtor( + Constructor ctor, Map args) { + try { + return ctor.newInstance(args); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException ex) { + Logger.getLogger(ConfigurationSerialization.class.getName()).log(Level.SEVERE, + "Could not call constructor '" + ctor.toString() + "' of " + this.clazz + + " for deserialization", + ex instanceof InvocationTargetException ? ex.getCause() : ex); + } + + return null; + } + + public ConfigurationSerializable deserialize(Map args) { + if (args == null) { + throw new NullPointerException("Args must not be null"); + } + ConfigurationSerializable result = null; + Method method = getMethod("deserialize", true); + if (method != null) { + result = deserializeViaMethod(method, args); + } + if (result == null) { + method = getMethod("valueOf", true); + if (method != null) { + result = deserializeViaMethod(method, args); + } + } + if (result == null) { + Constructor constructor = getConstructor(); + if (constructor != null) { + result = deserializeViaCtor(constructor, args); + } + } + + return result; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/DelegateDeserialization.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/DelegateDeserialization.java new file mode 100644 index 000000000..8a071cb2e --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/DelegateDeserialization.java @@ -0,0 +1,21 @@ +package com.github.intellectualsites.plotsquared.configuration.serialization; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Applies to a {@link ConfigurationSerializable} that will delegate all + * deserialization to another {@link ConfigurationSerializable}. + */ +@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) +public @interface DelegateDeserialization { + /** + * Which class should be used as a delegate for this classes + * deserialization + * + * @return Delegate class + */ + Class value(); +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/SerializableAs.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/SerializableAs.java new file mode 100644 index 000000000..79b50998c --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/configuration/serialization/SerializableAs.java @@ -0,0 +1,32 @@ +package com.github.intellectualsites.plotsquared.configuration.serialization; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Represents an "alias" that a {@link ConfigurationSerializable} may be + * stored as. + * If this is not present on a {@link ConfigurationSerializable} class, it + * will use the fully qualified name of the class. + *

+ * This value will be stored in the configuration so that the configuration + * deserialization can determine what type it is. + *

+ * Using this annotation on any other class than a {@link + * ConfigurationSerializable} will have no effect. + * + * @see ConfigurationSerialization#registerClass(Class, String) + */ +@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface SerializableAs { + /** + * This is the name your class will be stored and retrieved as. + *

+ * This name MUST be unique. We recommend using names such as + * "MyPluginThing" instead of "Thing". + * + * @return Name to serialize the class as. + */ + String value(); +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONArray.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONArray.java new file mode 100644 index 000000000..62ee01329 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONArray.java @@ -0,0 +1,815 @@ +package com.github.intellectualsites.plotsquared.json; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +/** + * A JSONArray is an ordered sequence of values. Its external text form is a string wrapped in square brackets with + * commas separating the values. The internal form is an object having {@code get} and {@code opt} methods for + * accessing the values by index, and {@code put} methods for adding or replacing values. The values can be any of + * these types: {@code Boolean}, {@code JSONArray}, {@code JSONObject}, {@code Number}, + * {@code String}, or the {@code JSONObject.NULL object}. + * + *

The constructor can convert a JSON text into a Java object. The {@code toString} method converts to JSON text. + * + *

A {@code get} method returns a value if one can be found, and throws an exception if one cannot be found. An + * {@code opt} method returns a default value instead of throwing an exception, and so is useful for obtaining + * optional values. + * + *

The generic {@code get()} and {@code opt()} methods return an object which you can cast or query for type. + * There are also typed {@code get} and {@code opt} methods that do type checking and type coercion for you. + * + *

The texts produced by the {@code toString} methods strictly conform to JSON syntax rules. The constructors are + * more forgiving in the texts they will accept:

  • An extra {@code ,} (comma) may appear + * just before the closing bracket.
  • The {@code null} value will be inserted when there is {@code ,} + *  (comma) elision.
  • Strings may be quoted with {@code '} (single + * quote).
  • Strings do not need to be quoted at all if they do not begin with a quote or single quote, + * and if they do not contain leading or trailing spaces, and if they do not contain any of these characters: {@code { } + * [ ] / \ : , #} and if they do not look like numbers and if they are not the reserved words {@code true}, + * {@code false}, or {@code null}.
+ * + * @author JSON.org + * @version 2014-05-03 + */ +public class JSONArray { + /** + * The arrayList where the JSONArray's properties are kept. + */ + private final ArrayList myArrayList; + + /** + * Construct an empty JSONArray. + */ + public JSONArray() { + this.myArrayList = new ArrayList(); + } + + /** + * Construct a JSONArray from a JSONTokener. + * + * @param x A JSONTokener + * @throws JSONException If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(); + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); + } + if (x.nextClean() != ']') { + x.back(); + for (; ; ) { + if (x.nextClean() == ',') { + x.back(); + this.myArrayList.add(JSONObject.NULL); + } else { + x.back(); + this.myArrayList.add(x.nextValue()); + } + switch (x.nextClean()) { + case ',': + if (x.nextClean() == ']') { + return; + } + x.back(); + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + } + + /** + * Construct a JSONArray from a source JSON text. + * + * @param source A string that begins with {@code [} (left bracket) and ends with + * {@code ]}  (right bracket). + * @throws JSONException If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONArray from a Collection. + * + * @param collection A Collection. + */ + public JSONArray(Collection collection) { + this.myArrayList = new ArrayList(); + if (collection != null) { + for (Object aCollection : collection) { + this.myArrayList.add(JSONObject.wrap(aCollection)); + } + } + } + + /** + * Construct a JSONArray from an array + * + * @throws JSONException If not an array. + */ + public JSONArray(Object array) throws JSONException { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + for (int i = 0; i < length; i += 1) { + this.put(JSONObject.wrap(Array.get(array, i))); + } + } else { + throw new JSONException( + "JSONArray initial value should be a string or collection or array."); + } + } + + /** + * Get the object value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return An object value. + * @throws JSONException If there is no value for the index. + */ + public Object get(int index) throws JSONException { + Object object = opt(index); + if (object == null) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with an index. The string values "true" and "false" are converted to boolean. + * + * @param index The index must be between 0 and length() - 1. + * @return The truth. + * @throws JSONException If there is no value for the index or if the value is not convertible to boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = get(index); + if (object.equals(Boolean.FALSE) || ((object instanceof String) && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) || ((object instanceof String) && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONArray[" + index + "] is not a boolean."); + } + + /** + * Get the double value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot be converted to a number. + */ + public double getDouble(int index) throws JSONException { + Object object = get(index); + try { + return object instanceof Number ? + ((Number) object).doubleValue() : + Double.parseDouble((String) object); + } catch (NumberFormatException ignored) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the int value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value is not a number. + */ + public int getInt(int index) throws JSONException { + Object object = get(index); + try { + return object instanceof Number ? + ((Number) object).intValue() : + Integer.parseInt((String) object); + } catch (NumberFormatException ignored) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the JSONArray associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return A JSONArray value. + * @throws JSONException If there is no value for the index. or if the value is not a JSONArray + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); + } + + /** + * Get the JSONObject associated with an index. + * + * @param index subscript + * @return A JSONObject value. + * @throws JSONException If there is no value for the index or if the value is not a JSONObject + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONObject."); + } + + /** + * Get the long value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot be converted to a number. + */ + public long getLong(int index) throws JSONException { + Object object = get(index); + try { + return object instanceof Number ? + ((Number) object).longValue() : + Long.parseLong((String) object); + } catch (NumberFormatException ignored) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the string associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return A string value. + * @throws JSONException If there is no string value for the index. + */ + public String getString(int index) throws JSONException { + Object object = get(index); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONArray[" + index + "] not a string."); + } + + /** + * Determine if the value is null. + * + * @param index The index must be between 0 and length() - 1. + * @return true if the value at the index is null, or if there is no value. + */ + public boolean isNull(int index) { + return JSONObject.NULL.equals(opt(index)); + } + + /** + * Make a string from the contents of this JSONArray. The {@code separator} string is inserted between each + * element. Warning: This method assumes that the data structure is acyclical. + * + * @param separator A string that will be inserted between the elements. + * @return a string. + * @throws JSONException If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + int len = length(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(separator); + } + sb.append(JSONObject.valueToString(this.myArrayList.get(i))); + } + return sb.toString(); + } + + /** + * Get the number of elements in the JSONArray, included nulls. + * + * @return The length (or size). + */ + public int length() { + return this.myArrayList.size(); + } + + /** + * Get the optional object value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return An object value, or null if there is no object at that index. + */ + public Object opt(int index) { + return ((index < 0) || (index >= length())) ? null : this.myArrayList.get(index); + } + + /** + * Get the optional boolean value associated with an index. It returns false if there is no value at that index, or + * if the value is not Boolean.TRUE or the String "true". + * + * @param index The index must be between 0 and length() - 1. + * @return The truth. + */ + public boolean optBoolean(int index) { + return this.optBoolean(index, false); + } + + /** + * Get the optional boolean value associated with an index. It returns the defaultValue if there is no value at that + * index or if it is not a Boolean or the String "true" or "false" (case insensitive). + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue A boolean default. + * @return The truth. + */ + public boolean optBoolean(int index, boolean defaultValue) { + try { + return getBoolean(index); + } catch (JSONException ignored) { + return defaultValue; + } + } + + /** + * Get the optional double value associated with an index. NaN is returned if there is no value for the index, or if + * the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public double optDouble(int index) { + return this.optDouble(index, Double.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue is returned if there is no value for the + * index, or if the value is not a number and cannot be converted to a number. + * + * @param index subscript + * @param defaultValue The default value. + * @return The value. + */ + public double optDouble(int index, double defaultValue) { + try { + return getDouble(index); + } catch (JSONException ignored) { + return defaultValue; + } + } + + /** + * Get the optional int value associated with an index. Zero is returned if there is no value for the index, or if + * the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public int optInt(int index) { + return this.optInt(index, 0); + } + + /** + * Get the optional int value associated with an index. The defaultValue is returned if there is no value for the + * index, or if the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return The value. + */ + public int optInt(int index, int defaultValue) { + try { + return getInt(index); + } catch (JSONException ignored) { + return defaultValue; + } + } + + /** + * Get the optional JSONArray associated with an index. + * + * @param index subscript + * @return A JSONArray value, or null if the index has no value, or if the value is not a JSONArray. + */ + public JSONArray optJSONArray(int index) { + Object o = opt(index); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get the optional JSONObject associated with an index. Null is returned if the key is not found, or null if the + * index has no value, or if the value is not a JSONObject. + * + * @param index The index must be between 0 and length() - 1. + * @return A JSONObject value. + */ + public JSONObject optJSONObject(int index) { + Object o = opt(index); + return o instanceof JSONObject ? (JSONObject) o : null; + } + + /** + * Get the optional long value associated with an index. Zero is returned if there is no value for the index, or if + * the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public long optLong(int index) { + return this.optLong(index, 0); + } + + /** + * Get the optional long value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return The value. + */ + public long optLong(int index, long defaultValue) { + try { + return getLong(index); + } catch (JSONException ignored) { + return defaultValue; + } + } + + /** + * Get the optional string value associated with an index. It returns an + * empty string if there is no value at that index. If the value is not a + * string and is not null, then it is converted to a string. + * + * @param index The index must be between 0 and length() - 1. + * @return A String value. + */ + public String optString(int index) { + return this.optString(index, ""); + } + + /** + * Get the optional string associated with an index. The defaultValue is + * returned if the key is not found. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return A String value. + */ + public String optString(int index, String defaultValue) { + Object object = opt(index); + return JSONObject.NULL.equals(object) ? defaultValue : object.toString(); + } + + /** + * Append a boolean value. This increases the array's length by one. + * + * @param value A boolean value. + * @return this. + */ + public JSONArray put(boolean value) { + this.put(value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param value A Collection value. + * @return this. + */ + public JSONArray put(Collection value) { + this.put(new JSONArray(value)); + return this; + } + + /** + * Append a double value. This increases the array's length by one. + * + * @param value A double value. + * @return this. + * @throws JSONException if the value is not finite. + */ + public JSONArray put(double value) throws JSONException { + Double d = value; + JSONObject.testValidity(d); + this.put(d); + return this; + } + + /** + * Append an int value. This increases the array's length by one. + * + * @param value An int value. + * @return this. + */ + public JSONArray put(int value) { + this.put(Integer.valueOf(value)); + return this; + } + + /** + * Append an long value. This increases the array's length by one. + * + * @param value A long value. + * @return this. + */ + public JSONArray put(long value) { + this.put(Long.valueOf(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject which + * is produced from a Map. + * + * @param value A Map value. + * @return this. + */ + public JSONArray put(Map value) { + this.put(new JSONObject(value)); + return this; + } + + /** + * Append an object value. This increases the array's length by one. + * + * @param value An object value. The value should be a Boolean, Double, + * Integer, JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. + * @return this. + */ + public JSONArray put(Object value) { + this.myArrayList.add(value); + return this; + } + + /** + * Put or replace a boolean value in the JSONArray. If the index is greater than the length of the JSONArray, then + * null elements will be added as necessary to pad it out. + * + * @param index The subscript. + * @param value A boolean value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, boolean value) throws JSONException { + this.put(index, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param index The subscript. + * @param value A Collection value. + * @return this. + * @throws JSONException If the index is negative or if the value is not + * finite. + */ + public JSONArray put(int index, Collection value) throws JSONException { + this.put(index, new JSONArray(value)); + return this; + } + + /** + * Put or replace a double value. If the index is greater than the length + * of the JSONArray, then null elements will be added as necessary to pad + * it out. + * + * @param index The subscript. + * @param value A double value. + * @return this. + * @throws JSONException If the index is negative or if the value is not + * finite. + */ + public JSONArray put(int index, double value) throws JSONException { + this.put(index, new Double(value)); + return this; + } + + /** + * Put or replace an int value. If the index is greater than the length of the JSONArray, then null elements will be + * added as necessary to pad it out. + * + * @param index The subscript. + * @param value An int value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, int value) throws JSONException { + this.put(index, Integer.valueOf(value)); + return this; + } + + /** + * Put or replace a long value. If the index is greater than the length of the JSONArray, then null elements will be + * added as necessary to pad it out. + * + * @param index The subscript. + * @param value A long value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, long value) throws JSONException { + this.put(index, Long.valueOf(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject that + * is produced from a Map. + * + * @param index The subscript. + * @param value The Map value. + * @return this. + * @throws JSONException If the index is negative or if the the value is an + * invalid number. + */ + public JSONArray put(int index, Map value) throws JSONException { + this.put(index, new JSONObject(value)); + return this; + } + + /** + * Put or replace an object value in the JSONArray. If the index is greater than the length of the JSONArray, then + * null elements will be added as necessary to pad it out. + * + * @param index The subscript. + * @param value The value to put into the array. The value should be a Boolean, Double, Integer, JSONArray, + * JSONObject, Long, or String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException If the index is negative or if the the value is an invalid number. + */ + public JSONArray put(int index, Object value) throws JSONException { + JSONObject.testValidity(value); + if (index < 0) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + if (index < length()) { + this.myArrayList.set(index, value); + } else { + while (index != length()) { + this.put(JSONObject.NULL); + } + this.put(value); + } + return this; + } + + /** + * Remove an index and close the hole. + * + * @param index The index of the element to be removed. + * @return The value that was associated with the index, or null if there was no value. + */ + public Object remove(int index) { + return (index >= 0) && (index < length()) ? this.myArrayList.remove(index) : null; + } + + /** + * Determine if two JSONArrays are similar. They must contain similar sequences. + * + * @param other The other JSONArray + * @return true if they are equal + */ + public boolean similar(Object other) { + if (!(other instanceof JSONArray)) { + return false; + } + int len = length(); + if (len != ((JSONArray) other).length()) { + return false; + } + for (int i = 0; i < len; i += 1) { + Object valueThis = get(i); + Object valueOther = ((JSONArray) other).get(i); + if (valueThis instanceof JSONObject) { + if (!((JSONObject) valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray) valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } + } + return true; + } + + /** + * Produce a JSONObject by combining a JSONArray of names with the values of this JSONArray. + * + * @param names A JSONArray containing a list of key strings. These will be paired with the values. + * @return A JSONObject, or null if there are no names or if this JSONArray has no values. + * @throws JSONException If any of the names are null. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if ((names == null) || (names.length() == 0) || (length() == 0)) { + return null; + } + JSONObject jo = new JSONObject(); + for (int i = 0; i < names.length(); i += 1) { + jo.put(names.getString(i), opt(i)); + } + return jo; + } + + /** + * Make a JSON text of this JSONArray. For compactness, no unnecessary whitespace is added. If it is not possible to + * produce a syntactically correct JSON text then null will be returned instead. This could occur if the array + * contains an invalid number. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, transmittable representation of the array. + */ + @Override public String toString() { + try { + return this.toString(0); + } catch (JSONException ignored) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONArray. Warning: This method assumes that the data structure is + * acyclical. + * + * @param indentFactor The number of spaces to add to each level of indentation. + * @return a printable, displayable, transmittable representation of the object, beginning with + * {@code [} (left bracket) and ending with {@code ]}  (right + * bracket). + * @throws JSONException + */ + public String toString(int indentFactor) throws JSONException { + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + return this.write(sw, indentFactor, 0).toString(); + } + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of indentation. + * @param indent The indention of the top level. + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) throws JSONException { + try { + boolean commanate = false; + int length = length(); + writer.write('['); + if (length == 1) { + JSONObject.writeValue(writer, this.myArrayList.get(0), indentFactor, indent); + } else if (length != 0) { + int newindent = indent + indentFactor; + for (int i = 0; i < length; i += 1) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, newindent); + JSONObject.writeValue(writer, this.myArrayList.get(i), indentFactor, newindent); + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, indent); + } + writer.write(']'); + return writer; + } catch (IOException e) { + throw new JSONException(e); + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONException.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONException.java new file mode 100644 index 000000000..67de10815 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONException.java @@ -0,0 +1,40 @@ +package com.github.intellectualsites.plotsquared.json; + +/** + * The JSONException is thrown by the JSON.org classes when things are amiss. + * + * @author JSON.org + * @version 2014-05-03 + */ +public class JSONException extends RuntimeException { + private static final long serialVersionUID = 0; + private Throwable cause; + + /** + * Constructs a JSONException with an explanatory message. + * + * @param message Detail about the reason for the exception. + */ + public JSONException(final String message) { + super(message); + } + + /** + * Constructs a new JSONException with the specified cause. + * + * @param cause The cause. + */ + public JSONException(final Throwable cause) { + super(cause.getMessage()); + this.cause = cause; + } + + /** + * Returns the cause of this exception or null if the cause is nonexistent or unknown. + * + * @return the cause of this exception or null if the cause is nonexistent or unknown. + */ + @Override public Throwable getCause() { + return cause; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONObject.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONObject.java new file mode 100644 index 000000000..9a84920dc --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONObject.java @@ -0,0 +1,1425 @@ +package com.github.intellectualsites.plotsquared.json; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.Map.Entry; + +/** + * A JSONObject is an unordered collection of name/value pairs. Its external form is a string wrapped in curly braces + * with colons between the names and values, and commas between the values and names. The internal form is an object + * having get and opt methods for accessing the values by name, and put methods + * for adding or replacing values by name. The values can be any of these types: Boolean, + * JSONArray, JSONObject, Number, String, or the + * JSONObject.NULL object. A JSONObject constructor can be used to convert an external form JSON text into + * an internal form whose values can be retrieved with the get and opt methods, or to convert + * values into a JSON text using the put and toString methods. A get method + * returns a value if one can be found, and throws an exception if one cannot be found. An opt method + * returns a default value instead of throwing an exception, and so is useful for obtaining optional values. + *

+ * The generic get() and opt() methods return an object, which you can cast or query for type. + * There are also typed get and opt methods that do type checking and type coercion for you. + * The opt methods differ from the get methods in that they do not throw. Instead, they return a specified value, such + * as null. + *

+ * The put methods add or replace values in an object. For example, + * + * + *

+ * myString = new JSONObject().put("JSON", "Hello, World!").toString();
+ * 
+ *

+ * produces the string {"JSON": "Hello, World"}. + *

+ * The texts produced by the toString methods strictly conform to the JSON syntax rules. The constructors + * are more forgiving in the texts they will accept:

  • An extra , (comma) may + * appear just before the closing brace.
  • Strings may be quoted with ' (single + * quote).
  • Strings do not need to be quoted at all if they do not begin with a quote or single quote, + * and if they do not contain leading or trailing spaces, and if they do not contain any of these characters: { } + * [ ] / \ : , # and if they do not look like numbers and if they are not the reserved words true, + * false, or null.
+ * + * @author JSON.org + * @version 2014-05-03 + */ +public class JSONObject { + /** + * It is sometimes more convenient and less ambiguous to have a NULL object than to use Java's + * null value. JSONObject.NULL.equals(null) returns true. + * JSONObject.NULL.toString() returns "null". + */ + public static final Object NULL = new Null(); + /** + * The map where the JSONObject's properties are kept. + */ + private final Map map; + + /** + * Construct an empty JSONObject. + */ + public JSONObject() { + this.map = new HashMap(); + } + + /** + * Construct a JSONObject from a subset of another JSONObject. An array of strings is used to identify the keys that + * should be copied. Missing keys are ignored. + * + * @param jo A JSONObject. + * @param names An array of strings. + * @throws JSONException + * @throws JSONException If a value is a non-finite number or if a name is duplicated. + */ + public JSONObject(JSONObject jo, String[] names) { + this(); + for (String name : names) { + try { + putOnce(name, jo.opt(name)); + } catch (JSONException ignore) { + } + } + } + + /** + * Construct a JSONObject from a JSONTokener. + * + * @param x A JSONTokener object containing the source string. + * @throws JSONException If there is a syntax error in the source string or a duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (; ; ) { + c = x.nextClean(); + switch (c) { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + // The key is followed by ':'. + c = x.nextClean(); + if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + putOnce(key, x.nextValue()); + // Pairs are separated by ','. + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * Construct a JSONObject from a Map. + * + * @param map A map object that can be used to initialize the contents of the JSONObject. + * @throws JSONException + */ + public JSONObject(Map map) { + this.map = new HashMap(); + if (map != null) { + for (Entry entry : map.entrySet()) { + Object value = entry.getValue(); + if (value != null) { + this.map.put(entry.getKey(), wrap(value)); + } + } + } + } + + /** + * Construct a JSONObject from an Object using bean getters. It reflects on all of the public methods of the object. + * For each of the methods with no parameters and a name starting with "get" or "is" + * followed by an uppercase letter, the method is invoked, and a key and the value returned from the getter method + * are put into the new JSONObject. + *

+ * The key is formed by removing the "get" or "is" prefix. If the second remaining + * character is not upper case, then the first character is converted to lower case. + *

+ * For example, if an object has a method named "getPluginName", and if the result of calling + * object.getPluginName() is "Larry Fine", then the JSONObject will contain "name": "Larry + * Fine". + * + * @param bean An object that has getter methods that should be used to make a JSONObject. + */ + public JSONObject(Object bean) { + this(); + populateMap(bean); + } + + /** + * Construct a JSONObject from an Object, using reflection to find the public members. The resulting JSONObject's + * keys will be the strings from the names array, and the values will be the field values associated with those keys + * in the object. If a key is not found or not visible, then it will not be copied into the new JSONObject. + * + * @param object An object that has fields that should be used to make a JSONObject. + * @param names An array of strings, the names of the fields to be obtained from the object. + */ + public JSONObject(Object object, String names[]) { + this(); + Class c = object.getClass(); + for (String name : names) { + try { + putOpt(name, c.getField(name).get(object)); + } catch (JSONException | SecurityException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException ignore) { + } + } + } + + /** + * Construct a JSONObject from a source JSON text string. This is the most commonly used JSONObject constructor. + * + * @param source A string beginning with { (left brace) and ending with + * }  (right brace). + * @throws JSONException If there is a syntax error in the source string or a duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONObject from a ResourceBundle. + * + * @param baseName The ResourceBundle base name. + * @param locale The Locale to load the ResourceBundle for. + * @throws JSONException If any JSONExceptions are detected. + */ + public JSONObject(String baseName, Locale locale) throws JSONException { + this(); + ResourceBundle bundle = ResourceBundle + .getBundle(baseName, locale, Thread.currentThread().getContextClassLoader()); + // Iterate through the keys in the bundle. + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + if (key != null) { + // Go through the path, ensuring that there is a nested + // JSONObject for each + // segment except the last. Add the value using the last + // segment's name into + // the deepest nested JSONObject. + String[] path = ((String) key).split("\\."); + int last = path.length - 1; + JSONObject target = this; + for (int i = 0; i < last; i += 1) { + String segment = path[i]; + JSONObject nextTarget = target.optJSONObject(segment); + if (nextTarget == null) { + nextTarget = new JSONObject(); + target.put(segment, nextTarget); + } + target = nextTarget; + } + target.put(path[last], bundle.getString((String) key)); + } + } + } + + /** + * Produce a string from a double. The string "null" will be returned if the number is not finite. + * + * @param d A double. + * @return A String. + */ + public static String doubleToString(double d) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; + } + // Shave off trailing zeros and decimal point, if possible. + String string = Double.toString(d); + if ((string.indexOf('.') > 0) && (string.indexOf('e') < 0) && (string.indexOf('E') < 0)) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get an array of field names from a JSONObject. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + int length = jo.length(); + if (length == 0) { + return null; + } + Iterator iterator = jo.keys(); + String[] names = new String[length]; + int i = 0; + while (iterator.hasNext()) { + names[i] = iterator.next(); + i += 1; + } + return names; + } + + /** + * Get an array of field names from an Object. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Class klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + /** + * Produce a string from a Number. + * + * @param number A Number + * @return A String. + * @throws JSONException If n is a non-finite number. + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Null pointer"); + } + testValidity(number); + // Shave off trailing zeros and decimal point, if possible. + String string = number.toString(); + if ((string.indexOf('.') > 0) && (string.indexOf('e') < 0) && (string.indexOf('E') < 0)) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Produce a string in double quotes with backslash sequences in all the right places. A backslash will be inserted + * control character or an unescaped quote or backslash. + * + * @param string A String + * @return A String correctly formatted for insertion in a JSON text. + */ + public static String quote(String string) { + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + try { + return quote(string, sw).toString(); + } catch (IOException ignored) { + // will never happen - we are writing to a string writer + return ""; + } + } + } + + public static Writer quote(String string, Writer w) throws IOException { + if ((string == null) || string.isEmpty()) { + w.write("\"\""); + return w; + } + char b; + char c = 0; + String hhhh; + int i; + int len = string.length(); + w.write('"'); + for (i = 0; i < len; i += 1) { + b = c; + c = string.charAt(i); + switch (c) { + case '\\': + case '"': + w.write('\\'); + w.write(c); + break; + case '/': + if (b == '<') { + w.write('\\'); + } + w.write(c); + break; + case '\b': + w.write("\\b"); + break; + case '\t': + w.write("\\t"); + break; + case '\n': + w.write("\\n"); + break; + case '\f': + w.write("\\f"); + break; + case '\r': + w.write("\\r"); + break; + default: + if ((c < ' ') || ((c >= '\u0080') && (c < '\u00a0')) || ((c >= '\u2000') && (c + < '\u2100'))) { + w.write("\\u"); + hhhh = Integer.toHexString(c); + w.write("0000", 0, 4 - hhhh.length()); + w.write(hhhh); + } else { + w.write(c); + } + } + } + w.write('"'); + return w; + } + + /** + * Try to convert a string into a number, boolean, or null. If the string can't be converted, return the string. + * + * @param string A String. + * @return A simple JSON value. + */ + public static Object stringToValue(String string) { + Double d; + if (string.isEmpty()) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + return JSONObject.NULL; + } + /* + * If it might be a number, try converting it. If a number cannot be + * produced, then the value will just be a string. + */ + char b = string.charAt(0); + if (((b >= '0') && (b <= '9')) || (b == '-')) { + try { + if ((string.indexOf('.') > -1) || (string.indexOf('e') > -1) || (string.indexOf('E') + > -1)) { + d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = Long.valueOf(string); + if (string.equals(myLong.toString())) { + if (myLong == myLong.intValue()) { + return myLong.intValue(); + } else { + return myLong; + } + } + } + } catch (NumberFormatException ignore) { + } + } + return string; + } + + /** + * Throw an exception if the object is a NaN or infinite number. + * + * @param o The object to test. + * @throws JSONException If o is a non-finite number. + */ + public static void testValidity(Object o) throws JSONException { + if (o != null) { + if (o instanceof Double) { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) { + throw new JSONException("JSON does not allow non-finite numbers."); + } + } else if (o instanceof Float) { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) { + throw new JSONException("JSON does not allow non-finite numbers."); + } + } + } + } + + /** + * Make a JSON text of an Object value. If the object has an value.toJSONString() method, then that method will be + * used to produce the JSON text. The method is required to produce a strictly conforming text. If the object does + * not contain a toJSONString method (which is the most common case), then a text will be produced by other means. + * If the value is an array or Collection, then a JSONArray will be made from it and its toJSONString method will be + * called. If the value is a MAP, then a JSONObject will be made from it and its toJSONString method will be called. + * Otherwise, the value's toString method will be called, and the result will be quoted. + *

+ *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param value The value to be serialized. + * @return a printable, displayable, transmittable representation of the object, beginning with + * { (left brace) and ending with } (right + * brace). + * @throws JSONException If the value is or contains an invalid number. + */ + public static String valueToString(Object value) throws JSONException { + if ((value == null) || value.equals(null)) { + return "null"; + } + if (value instanceof JSONString) { + Object object; + try { + object = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (object != null) { + return (String) object; + } + throw new JSONException("Bad value from toJSONString: " + object); + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if ((value instanceof Boolean) || (value instanceof JSONObject) + || (value instanceof JSONArray)) { + return value.toString(); + } + if (value instanceof Map) { + return new JSONObject((Map) value).toString(); + } + if (value instanceof Collection) { + return new JSONArray((Collection) value).toString(); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(); + } + return quote(value.toString()); + } + + /** + * Wrap an object, if necessary. If the object is null, return the NULL object. If it is an array or collection, + * wrap it in a JSONArray. If it is a map, wrap it in a JSONObject. If it is a standard property (Double, String, et + * al) then it is already wrapped. Otherwise, if it comes from one of the java packages, turn it into a string. And + * if it doesn't, try to wrap it in a JSONObject. If the wrapping fails, then null is returned. + * + * @param object The object to wrap + * @return The wrapped value + */ + public static Object wrap(Object object) { + try { + if (object == null) { + return NULL; + } + if ((object instanceof JSONObject) || (object instanceof JSONArray) || NULL + .equals(object) || (object instanceof JSONString) || (object instanceof Byte) + || (object instanceof Character) || (object instanceof Short) + || (object instanceof Integer) || (object instanceof Long) + || (object instanceof Boolean) || (object instanceof Float) + || (object instanceof Double) || (object instanceof String)) { + return object; + } + if (object instanceof Collection) { + return new JSONArray((Collection) object); + } + if (object.getClass().isArray()) { + return new JSONArray(object); + } + if (object instanceof Map) { + return new JSONObject((Map) object); + } + Package objectPackage = object.getClass().getPackage(); + String objectPackageName = objectPackage != null ? objectPackage.getName() : ""; + if (objectPackageName.startsWith("java.") || objectPackageName.startsWith("javax.") || ( + object.getClass().getClassLoader() == null)) { + return object.toString(); + } + return new JSONObject(object); + } catch (JSONException ignored) { + return null; + } + } + + static final Writer writeValue(Writer writer, Object value, int indentFactor, int indent) + throws JSONException, IOException { + if ((value == null) || value.equals(null)) { + writer.write("null"); + } else if (value instanceof JSONObject) { + ((JSONObject) value).write(writer, indentFactor, indent); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer, indentFactor, indent); + } else if (value instanceof Map) { + new JSONObject((Map) value).write(writer, indentFactor, indent); + } else if (value instanceof Collection) { + new JSONArray((Collection) value).write(writer, indentFactor, indent); + } else if (value.getClass().isArray()) { + new JSONArray(value).write(writer, indentFactor, indent); + } else if (value instanceof Number) { + writer.write(numberToString((Number) value)); + } else if (value instanceof Boolean) { + writer.write(value.toString()); + } else if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + writer.write(o != null ? o.toString() : quote(value.toString())); + } else { + quote(value.toString(), writer); + } + return writer; + } + + static final void indent(Writer writer, int indent) throws IOException { + for (int i = 0; i < indent; i += 1) { + writer.write(' '); + } + } + + /** + * Accumulate values under a key. It is similar to the put method except that if there is already an object stored + * under the key then a JSONArray is stored under the key to hold all of the accumulated values. If there is already + * a JSONArray, then the new value is appended to it. In contrast, the put method replaces the previous value. + *

+ * If only one value is accumulated that is not a JSONArray, then the result will be the same as using put. But if + * multiple values are accumulated, then the result will be like append. + * + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the value is an invalid number or if the key is null. + */ + public JSONObject accumulate(String key, Object value) throws JSONException { + testValidity(value); + Object object = opt(key); + if (object == null) { + this.put(key, value instanceof JSONArray ? new JSONArray().put(value) : value); + } else if (object instanceof JSONArray) { + ((JSONArray) object).put(value); + } else { + this.put(key, new JSONArray().put(object).put(value)); + } + return this; + } + + /** + * Append values to the array under a key. If the key does not exist in the JSONObject, then the key is put in the + * JSONObject with its value being a JSONArray containing the value parameter. If the key was already associated + * with a JSONArray, then the value parameter is appended to it. + * + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the key is null or if the current value associated with the key is not a JSONArray. + */ + public JSONObject append(String key, Object value) throws JSONException { + testValidity(value); + Object object = opt(key); + if (object == null) { + this.put(key, new JSONArray().put(value)); + } else if (object instanceof JSONArray) { + this.put(key, ((JSONArray) object).put(value)); + } else { + throw new JSONException("JSONObject[" + key + "] is not a JSONArray."); + } + return this; + } + + /** + * Get the value object associated with a key. + * + * @param key A key string. + * @return The object associated with the key. + * @throws JSONException if the key is not found. + */ + public Object get(String key) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + Object object = opt(key); + if (object == null) { + throw new JSONException("JSONObject[" + quote(key) + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with a key. + * + * @param key A key string. + * @return The truth. + * @throws JSONException if the value is not a Boolean or the String "true" or "false". + */ + public boolean getBoolean(String key) throws JSONException { + Object object = get(key); + if (object.equals(Boolean.FALSE) || ((object instanceof String) && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) || ((object instanceof String) && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a Boolean."); + } + + /** + * Get the double value associated with a key. + * + * @param key A key string. + * @return The numeric value. + * @throws JSONException if the key is not found or if the value is not a Number object and cannot be converted to a + * number. + */ + public double getDouble(String key) throws JSONException { + Object object = get(key); + try { + return object instanceof Number ? + ((Number) object).doubleValue() : + Double.parseDouble((String) object); + } catch (NumberFormatException ignored) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a number."); + } + } + + /** + * Get the int value associated with a key. + * + * @param key A key string. + * @return The integer value. + * @throws JSONException if the key is not found or if the value cannot be converted to an integer. + */ + public int getInt(String key) throws JSONException { + Object object = get(key); + try { + return object instanceof Number ? + ((Number) object).intValue() : + Integer.parseInt((String) object); + } catch (NumberFormatException ignored) { + throw new JSONException("JSONObject[" + quote(key) + "] is not an int."); + } + } + + /** + * Get the JSONArray value associated with a key. + * + * @param key A key string. + * @return A JSONArray which is the value. + * @throws JSONException if the key is not found or if the value is not a JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object object = get(key); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONArray."); + } + + /** + * Get the JSONObject value associated with a key. + * + * @param key A key string. + * @return A JSONObject which is the value. + * @throws JSONException if the key is not found or if the value is not a JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object object = get(key); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONObject."); + } + + /** + * Get the long value associated with a key. + * + * @param key A key string. + * @return The long value. + * @throws JSONException if the key is not found or if the value cannot be converted to a long. + */ + public long getLong(String key) throws JSONException { + Object object = get(key); + try { + return object instanceof Number ? + ((Number) object).longValue() : + Long.parseLong((String) object); + } catch (NumberFormatException ignored) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a long."); + } + } + + /** + * Get the string associated with a key. + * + * @param key A key string. + * @return A string which is the value. + * @throws JSONException if there is no string value for the key. + */ + public String getString(String key) throws JSONException { + Object object = get(key); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] not a string."); + } + + /** + * Determine if the JSONObject contains a specific key. + * + * @param key A key string. + * @return true if the key exists in the JSONObject. + */ + public boolean has(String key) { + return this.map.containsKey(key); + } + + /** + * Increment a property of a JSONObject. If there is no such property, create one with a value of 1. If there is + * such a property, and if it is an Integer, Long, Double, or Float, then add one to it. + * + * @param key A key string. + * @return this. + * @throws JSONException If there is already a property with this name that is not an Integer, Long, Double, or + * Float. + */ + public JSONObject increment(String key) throws JSONException { + Object value = opt(key); + if (value == null) { + this.put(key, 1); + } else if (value instanceof Integer) { + this.put(key, (Integer) value + 1); + } else if (value instanceof Long) { + this.put(key, (Long) value + 1); + } else if (value instanceof Double) { + this.put(key, (Double) value + 1); + } else if (value instanceof Float) { + this.put(key, (Float) value + 1); + } else { + throw new JSONException("Unable to increment [" + quote(key) + "]."); + } + return this; + } + + /** + * Determine if the value associated with the key is null or if there is no value. + * + * @param key A key string. + * @return true if there is no value associated with the key or if the value is the JSONObject.NULL object. + */ + public boolean isNull(String key) { + return JSONObject.NULL.equals(opt(key)); + } + + /** + * Get an enumeration of the keys of the JSONObject. + * + * @return An iterator of the keys. + */ + public Iterator keys() { + return keySet().iterator(); + } + + /** + * Get a set of keys of the JSONObject. + * + * @return A keySet. + */ + public Set keySet() { + return this.map.keySet(); + } + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() { + return this.map.size(); + } + + /** + * Produce a JSONArray containing the names of the elements of this JSONObject. + * + * @return A JSONArray containing the key strings, or null if the JSONObject is empty. + */ + public JSONArray names() { + JSONArray ja = new JSONArray(); + Iterator keys = keys(); + while (keys.hasNext()) { + ja.put(keys.next()); + } + return ja.length() == 0 ? null : ja; + } + + /** + * Get an optional value associated with a key. + * + * @param key A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) { + return key == null ? null : this.map.get(key); + } + + /** + * Get an optional boolean associated with a key. It returns false if there is no such key, or if the value is not + * Boolean.TRUE or the String "true". + * + * @param key A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return this.optBoolean(key, false); + } + + /** + * Get an optional boolean associated with a key. It returns the defaultValue if there is no such key, or if it is + * not a Boolean or the String "true" or "false" (case insensitive). + * + * @param key A key string. + * @param defaultValue The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + try { + return getBoolean(key); + } catch (JSONException ignored) { + return defaultValue; + } + } + + /** + * Get an optional double associated with a key, or NaN if there is no such key or if its value is not a number. If + * the value is a string, an attempt will be made to evaluate it as a number. + * + * @param key A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return this.optDouble(key, Double.NaN); + } + + /** + * Get an optional double associated with a key, or the defaultValue if there is no such key or if its value is not + * a number. If the value is a string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + try { + return getDouble(key); + } catch (JSONException ignored) { + return defaultValue; + } + } + + /** + * Get an optional int value associated with a key, or zero if there is no such key or if the value is not a number. + * If the value is a string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return this.optInt(key, 0); + } + + /** + * Get an optional int value associated with a key, or the default if there is no such key or if the value is not a + * number. If the value is a string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + try { + return getInt(key); + } catch (JSONException ignored) { + return defaultValue; + } + } + + /** + * Get an optional JSONArray associated with a key. It returns null if there is no such key, or if its value is not + * a JSONArray. + * + * @param key A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = opt(key); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get an optional JSONObject associated with a key. It returns null if there is no such key, or if its value is not + * a JSONObject. + * + * @param key A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object object = opt(key); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Get an optional long value associated with a key, or zero if there is no such key or if the value is not a + * number. If the value is a string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return this.optLong(key, 0); + } + + /** + * Get an optional long value associated with a key, or the default if there is no such key or if the value is not a + * number. If the value is a string, an attempt will be made to evaluate it as a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + try { + return getLong(key); + } catch (JSONException ignored) { + return defaultValue; + } + } + + /** + * Get an optional string associated with a key. It returns an empty string if there is no such key. If the value is + * not a string and is not null, then it is converted to a string. + * + * @param key A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return this.optString(key, ""); + } + + /** + * Get an optional string associated with a key. It returns the defaultValue if there is no such key. + * + * @param key A key string. + * @param defaultValue The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object object = opt(key); + return NULL.equals(object) ? defaultValue : object.toString(); + } + + private void populateMap(Object bean) { + Class klass = bean.getClass(); + // If klass is a System class then set includeSuperClass to false. + boolean includeSuperClass = klass.getClassLoader() != null; + Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods(); + for (Method method : methods) { + try { + if (Modifier.isPublic(method.getModifiers())) { + String name = method.getName(); + String key = ""; + if (name.startsWith("get")) { + if ("getClass".equals(name) || "getDeclaringClass".equals(name)) { + key = ""; + } else { + key = name.substring(3); + } + } else if (name.startsWith("is")) { + key = name.substring(2); + } + if (!key.isEmpty() && Character.isUpperCase(key.charAt(0)) && ( + method.getParameterTypes().length == 0)) { + if (key.length() == 1) { + key = key.toLowerCase(); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase() + key.substring(1); + } + Object result = method.invoke(bean, (Object[]) null); + if (result != null) { + this.map.put(key, wrap(result)); + } + } + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ignore) { + } + } + } + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key A key string. + * @param value A boolean which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, boolean value) throws JSONException { + this.put(key, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a JSONArray which is produced from a Collection. + * + * @param key A key string. + * @param value A Collection value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Collection value) throws JSONException { + this.put(key, new JSONArray(value)); + return this; + } + + /** + * Put a key/double pair in the JSONObject. + * + * @param key A key string. + * @param value A double which is the value. + * @return this. + * @throws JSONException If the key is null or if the number is invalid. + */ + public JSONObject put(String key, double value) throws JSONException { + this.put(key, new Double(value)); + return this; + } + + /** + * Put a key/int pair in the JSONObject. + * + * @param key A key string. + * @param value An int which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, int value) throws JSONException { + this.put(key, Integer.valueOf(value)); + return this; + } + + /** + * Put a key/long pair in the JSONObject. + * + * @param key A key string. + * @param value A long which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, long value) throws JSONException { + this.put(key, Long.valueOf(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a JSONObject which is produced from a Map. + * + * @param key A key string. + * @param value A Map value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Map value) throws JSONException { + this.put(key, new JSONObject(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject. If the value is null, then the key will be removed from the JSONObject if + * it is present. + * + * @param key A key string. + * @param value An object which is the value. It should be of one of these types: Boolean, Double, Integer, + * JSONArray, JSONObject, Long, String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is non-finite number or if the key is null. + */ + public JSONObject put(String key, Object value) throws JSONException { + if (key == null) { + throw new NullPointerException("Null key."); + } + if (value != null) { + testValidity(value); + this.map.put(key, value); + } else { + remove(key); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value are both non-null, and only if there is + * not already a member with that name. + * + * @param key string + * @param value object + * @return this. + * @throws JSONException if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if ((key != null) && (value != null)) { + if (opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + this.put(key, value); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value are both non-null. + * + * @param key A key string. + * @param value An object which is the value. It should be of one of these types: Boolean, Double, Integer, + * JSONArray, JSONObject, Long, String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) throws JSONException { + if ((key != null) && (value != null)) { + this.put(key, value); + } + return this; + } + + /** + * Remove a name and its value, if present. + * + * @param key The name to be removed. + * @return The value that was associated with the name, or null if there was no value. + */ + public Object remove(String key) { + return this.map.remove(key); + } + + /** + * Determine if two JSONObjects are similar. They must contain the same set of names which must be associated with + * similar values. + * + * @param other The other JSONObject + * @return true if they are equal + */ + public boolean similar(Object other) { + try { + if (!(other instanceof JSONObject)) { + return false; + } + Set set = keySet(); + if (!set.equals(((JSONObject) other).keySet())) { + return false; + } + for (String name : set) { + Object valueThis = get(name); + Object valueOther = ((JSONObject) other).get(name); + if (valueThis instanceof JSONObject) { + if (!((JSONObject) valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray) valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } + } + return true; + } catch (JSONException ignored) { + return false; + } + } + + /** + * Produce a JSONArray containing the values of the members of this JSONObject. + * + * @param names A JSONArray containing a list of key strings. This determines the sequence of the values in the + * result. + * @return A JSONArray of values. + * @throws JSONException If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if ((names == null) || (names.length() == 0)) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace is added. If this would not result in a + * syntactically correct JSON text, then null will be returned instead. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, portable, transmittable representation of the object, beginning with + * { (left brace) and ending with } (right + * brace). + */ + @Override public String toString() { + try { + return this.toString(0); + } catch (JSONException ignored) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONObject. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of indentation. + * @return a printable, displayable, portable, transmittable representation of the object, beginning with + * { (left brace) and ending with } (right + * brace). + * @throws JSONException If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException { + StringWriter w = new StringWriter(); + synchronized (w.getBuffer()) { + return this.write(w, indentFactor, 0).toString(); + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + Writer write(Writer writer, int indentFactor, int indent) throws JSONException { + try { + boolean commanate = false; + int length = length(); + Iterator keys = keys(); + writer.write('{'); + if (length == 1) { + Object key = keys.next(); + writer.write(quote(key.toString())); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, this.map.get(key), indentFactor, indent); + } else if (length != 0) { + int newindent = indent + indentFactor; + while (keys.hasNext()) { + Object key = keys.next(); + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, newindent); + writer.write(quote(key.toString())); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + writeValue(writer, this.map.get(key), indentFactor, newindent); + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, indent); + } + writer.write('}'); + return writer; + } catch (IOException exception) { + throw new JSONException(exception); + } + } + + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, whilst Java's null is equivalent to the + * value that JavaScript calls undefined. + */ + private static final class Null { + /** + * There is only intended to be a single instance of the NULL object, so the clone method returns itself. + * + * @return NULL. + */ + @Override protected final Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException ignored) { + return this; + } + } + + /** + * A Null object is equal to the null value and to itself. + * + * @param object An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object or null. + */ + @Override public boolean equals(Object object) { + return (object == null) || (object == this); + } + + /** + * Get the "null" string value. + * + * @return The string "null". + */ + @Override public String toString() { + return "null"; + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONString.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONString.java new file mode 100644 index 000000000..019776e58 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONString.java @@ -0,0 +1,16 @@ +package com.github.intellectualsites.plotsquared.json; + +/** + * The JSONString interface allows a toJSONString() method so that a class can change the + * behavior of JSONObject.toString(), JSONArray.toString(), and + * JSONWriter.value(Object). The toJSONString method will be used instead of the + * default behavior of using the Object's toString() method and quoting the result. + */ +public interface JSONString { + /** + * The toJSONString method allows a class to produce its own JSON serialization. + * + * @return A strictly syntactically correct JSON text. + */ + String toJSONString(); +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONTokener.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONTokener.java new file mode 100644 index 000000000..290b9ea0b --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/JSONTokener.java @@ -0,0 +1,384 @@ +package com.github.intellectualsites.plotsquared.json; + +import java.io.*; + +/** + * A JSONTokener takes a source string and extracts characters and tokens from it. It is used by the JSONObject and + * JSONArray constructors to parse JSON source strings. + * + * @author JSON.org + * @version 2014-05-03 + */ +public class JSONTokener { + private final Reader reader; + private long character; + private boolean eof; + private long index; + private long line; + private char previous; + private boolean usePrevious; + + /** + * Construct a JSONTokener from a Reader. + * + * @param reader A reader. + */ + public JSONTokener(final Reader reader) { + this.reader = reader.markSupported() ? reader : new BufferedReader(reader); + eof = false; + usePrevious = false; + previous = 0; + index = 0; + character = 1; + line = 1; + } + + /** + * Construct a JSONTokener from an InputStream. + * + * @param inputStream The source. + */ + public JSONTokener(final InputStream inputStream) throws JSONException { + this(new InputStreamReader(inputStream)); + } + + /** + * Construct a JSONTokener from a string. + * + * @param s A source string. + */ + public JSONTokener(final String s) { + this(new StringReader(s)); + } + + /** + * Get the hex value of a character (base16). + * + * @param c A character between '0' and '9' or between 'A' and 'F' or between 'a' and 'f'. + * @return An int between 0 and 15, or -1 if c was not a hex digit. + */ + public static int dehexchar(final char c) { + if ((c >= '0') && (c <= '9')) { + return c - '0'; + } + if ((c >= 'A') && (c <= 'F')) { + return c - ('A' - 10); + } + if ((c >= 'a') && (c <= 'f')) { + return c - ('a' - 10); + } + return -1; + } + + /** + * Back up one character. This provides a sort of lookahead capability, so that you can test for a digit or letter + * before attempting to parse the next number or identifier. + */ + public void back() throws JSONException { + if (usePrevious || (index <= 0)) { + throw new JSONException("Stepping back two steps is not supported"); + } + index -= 1; + character -= 1; + usePrevious = true; + eof = false; + } + + public boolean end() { + return eof && !usePrevious; + } + + /** + * Determine if the source string still contains characters that next() can consume. + * + * @return true if not yet at the end of the source. + */ + public boolean more() throws JSONException { + this.next(); + if (end()) { + return false; + } + back(); + return true; + } + + /** + * Get the next character in the source string. + * + * @return The next character, or 0 if past the end of the source string. + */ + public char next() throws JSONException { + int c; + if (usePrevious) { + usePrevious = false; + c = previous; + } else { + try { + c = reader.read(); + } catch (final IOException exception) { + throw new JSONException(exception); + } + if (c <= 0) { // End of stream + eof = true; + c = 0; + } + } + index += 1; + if (previous == '\r') { + line += 1; + character = c == '\n' ? 0 : 1; + } else if (c == '\n') { + line += 1; + character = 0; + } else { + character += 1; + } + previous = (char) c; + return previous; + } + + /** + * Consume the next character, and check that it matches a specified character. + * + * @param c The character to match. + * @return The character. + * @throws JSONException if the character does not match. + */ + public char next(final char c) throws JSONException { + final char n = this.next(); + if (n != c) { + throw syntaxError("Expected '" + c + "' and instead saw '" + n + "'"); + } + return n; + } + + /** + * Get the next n characters. + * + * @param n The number of characters to take. + * @return A string of n characters. + * @throws JSONException Substring bounds error if there are not n characters remaining in the source string. + */ + public String next(final int n) throws JSONException { + if (n == 0) { + return ""; + } + final char[] chars = new char[n]; + int pos = 0; + while (pos < n) { + chars[pos] = this.next(); + if (end()) { + throw syntaxError("Substring bounds error"); + } + pos += 1; + } + return new String(chars); + } + + /** + * Get the next char in the string, skipping whitespace. + * + * @return A character, or 0 if there are no more characters. + * @throws JSONException + */ + public char nextClean() throws JSONException { + for (; ; ) { + final char c = this.next(); + if ((c == 0) || (c > ' ')) { + return c; + } + } + } + + /** + * Return the characters up to the next close quote character. Backslash processing is done. The formal JSON format + * does not allow strings in single quotes, but an implementation is allowed to accept them. + * + * @param quote The quoting character, either "  (double quote) or ' + *  (single quote). + * @return A String. + * @throws JSONException Unterminated string. + */ + public String nextString(final char quote) throws JSONException { + char c; + final StringBuilder sb = new StringBuilder(); + for (; ; ) { + c = this.next(); + switch (c) { + case 0: + case '\n': + case '\r': + throw syntaxError("Unterminated string"); + case '\\': + c = this.next(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + sb.append((char) Integer.parseInt(this.next(4), 16)); + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw syntaxError("Illegal escape."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } + } + } + + /** + * Get the text up but not including the specified character or the end of line, whichever comes first. + * + * @param delimiter A delimiter character. + * @return A string. + */ + public String nextTo(final char delimiter) throws JSONException { + final StringBuilder sb = new StringBuilder(); + for (; ; ) { + final char c = this.next(); + if ((c == delimiter) || (c == 0) || (c == '\n') || (c == '\r')) { + if (c != 0) { + back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the text up but not including one of the specified delimiter characters or the end of line, whichever comes + * first. + * + * @param delimiters A set of delimiter characters. + * @return A string, trimmed. + */ + public String nextTo(final String delimiters) throws JSONException { + char c; + final StringBuilder sb = new StringBuilder(); + for (; ; ) { + c = this.next(); + if ((delimiters.indexOf(c) >= 0) || (c == 0) || (c == '\n') || (c == '\r')) { + if (c != 0) { + back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the + * JSONObject.NULL object. + * + * @return An object. + * @throws JSONException If syntax error. + */ + public Object nextValue() throws JSONException { + char c = nextClean(); + String string; + switch (c) { + case '"': + case '\'': + return nextString(c); + case '{': + back(); + return new JSONObject(this); + case '[': + back(); + return new JSONArray(this); + } + /* + * Handle unquoted text. This could be the values true, false, or + * null, or it can be a number. An implementation (such as this one) + * is allowed to also accept non-standard forms. + * Accumulate characters until we reach the end of the text or a + * formatting character. + */ + final StringBuilder sb = new StringBuilder(); + while ((c >= ' ') && (",:]}/\\\"[{;=#".indexOf(c) < 0)) { + sb.append(c); + c = this.next(); + } + back(); + string = sb.toString().trim(); + if (string.isEmpty()) { + throw syntaxError("Missing value"); + } + return JSONObject.stringToValue(string); + } + + /** + * Skip characters until the next character is the requested character. If the requested character is not found, no + * characters are skipped. + * + * @param to A character to skip to. + * @return The requested character, or zero if the requested character is not found. + */ + public char skipTo(final char to) throws JSONException { + char c; + try { + final long startIndex = index; + final long startCharacter = character; + final long startLine = line; + reader.mark(1000000); + do { + c = this.next(); + if (c == 0) { + reader.reset(); + index = startIndex; + character = startCharacter; + line = startLine; + return 0; + } + } while (c != to); + } catch (final IOException exception) { + throw new JSONException(exception); + } + back(); + return c; + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message The error message. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(final String message) { + return new JSONException(message + toString()); + } + + /** + * Make a printable string of this JSONTokener. + * + * @return " at {index} [character {character} line {line}]" + */ + @Override public String toString() { + return " at " + index + " [character " + character + " line " + line + "]"; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/Property.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/Property.java new file mode 100644 index 000000000..7ace1989b --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/Property.java @@ -0,0 +1,52 @@ +package com.github.intellectualsites.plotsquared.json; + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Properties; + +/** + * Converts a Property file data into JSONObject and back. + * + * @author JSON.org + * @version 2014-05-03 + */ +public class Property { + /** + * Converts a property file object into a JSONObject. The property file object is a table of name value pairs. + * + * @param properties java.util.Properties + * @return JSONObject + * @throws JSONException + */ + public static JSONObject toJSONObject(final java.util.Properties properties) + throws JSONException { + final JSONObject jo = new JSONObject(); + if ((properties != null) && !properties.isEmpty()) { + final Enumeration enumProperties = properties.propertyNames(); + while (enumProperties.hasMoreElements()) { + final String name = (String) enumProperties.nextElement(); + jo.put(name, properties.getProperty(name)); + } + } + return jo; + } + + /** + * Converts the JSONObject into a property file object. + * + * @param jo JSONObject + * @return java.util.Properties + * @throws JSONException + */ + public static Properties toProperties(final JSONObject jo) throws JSONException { + final Properties properties = new Properties(); + if (jo != null) { + final Iterator keys = jo.keys(); + while (keys.hasNext()) { + final String name = keys.next(); + properties.put(name, jo.getString(name)); + } + } + return properties; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/XML.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/XML.java new file mode 100644 index 000000000..51a0cdb10 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/XML.java @@ -0,0 +1,375 @@ +package com.github.intellectualsites.plotsquared.json; + +import java.util.Iterator; + +/** + * This provides static methods to convert an XML text into a JSONObject, and to covert a JSONObject into an XML text. + * + * @author JSON.org + * @version 2014-05-03 + */ +class XML { + + static final Character AMP = '&'; + static final Character APOS = '\''; + static final Character BANG = '!'; + static final Character EQ = '='; + static final Character GT = '>'; + static final Character LT = '<'; + static final Character QUEST = '?'; + static final Character QUOT = '"'; + static final Character SLASH = '/'; + + static String escape(String string) { + StringBuilder sb = new StringBuilder(string.length()); + for (int i = 0, length = string.length(); i < length; i++) { + char c = string.charAt(i); + switch (c) { + case '&': + sb.append("&"); + break; + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '"': + sb.append("""); + break; + case '\'': + sb.append("'"); + break; + default: + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Throw an exception if the string contains whitespace. Whitespace is not allowed in tagNames and attributes. + * + * @param string A string. + * @throws JSONException + */ + static void noSpace(String string) throws JSONException { + int length = string.length(); + if (length == 0) { + throw new JSONException("Empty string."); + } + for (char c : string.toCharArray()) { + if (Character.isWhitespace(c)) { + throw new JSONException('\'' + string + "' contains a space character."); + } + } + } + + /** + * Scan the content following the named tag, attaching it to the context. + * + * @param x The XMLTokener containing the source string. + * @param context The JSONObject that will include the new material. + * @param name The tag name. + * @return true if the close tag is processed. + * @throws JSONException + */ + private static boolean parse(XMLTokener x, JSONObject context, String name) + throws JSONException { + // Test for and skip past these forms: + // + // + // + // + // Report errors for these forms: + // <> + // <= + // << + Object token = x.nextToken(); + // "); + return false; + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if ("CDATA".equals(token)) { + if (x.next() == '[') { + string = x.nextCDATA(); + if (!string.isEmpty()) { + context.accumulate("content", string); + } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + int i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (token == QUEST) { + // "); + return false; + } else if (token == SLASH) { + // Close tag + } else if (token == SLASH) { + if (x.nextToken() != GT) { + throw x.syntaxError("Misshaped tag"); + } + if (jsonobject.length() > 0) { + context.accumulate(tagName, jsonobject); + } else { + context.accumulate(tagName, ""); + } + return false; + // Content, between <...> and + } else if (token == GT) { + for (; ; ) { + token = x.nextContent(); + if (token == null) { + if (tagName != null) { + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } else if (token instanceof String) { + string = (String) token; + if (!string.isEmpty()) { + jsonobject.accumulate("content", XML.stringToValue(string)); + } + // Nested element + } else if (token == LT) { + if (parse(x, jsonobject, tagName)) { + if (jsonobject.length() == 0) { + context.accumulate(tagName, ""); + } else if ((jsonobject.length() == 1) && (jsonobject.opt("content") + != null)) { + context.accumulate(tagName, jsonobject.opt("content")); + } else { + context.accumulate(tagName, jsonobject); + } + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + /** + * Try to convert a string into a number, boolean, or null. If the string can't be converted, return the string. + * This is much less ambitious than JSONObject.stringToValue, especially because it does not attempt to convert plus + * forms, octal forms, hex forms, or E forms lacking decimal points. + * + * @param string A String. + * @return A simple JSON value. + */ + static Object stringToValue(String string) { + if ("true".equalsIgnoreCase(string)) { + return Boolean.TRUE; + } + if ("false".equalsIgnoreCase(string)) { + return Boolean.FALSE; + } + if ("null".equalsIgnoreCase(string)) { + return JSONObject.NULL; + } + //If it might be a number, try converting it, first as a Long, and then as a Double. If that doesn't work, return the string. + try { + char initial = string.charAt(0); + if ((initial == '-') || ((initial >= '0') && (initial <= '9'))) { + Long value = Long.valueOf(string); + if (value.toString().equals(string)) { + return value; + } + } + } catch (NumberFormatException ignore) { + try { + Double value = Double.valueOf(string); + if (value.toString().equals(string)) { + return value; + } + } catch (NumberFormatException ignored) { + } + } + return string; + } + + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(string); + while (x.more() && x.skipPast("<")) { + parse(x, jo, null); + } + return jo; + } + + /** + * Convert a JSONObject into a well-formed, element-normal XML string. + * + * @param object A JSONObject. + * @return A string. + * @throws JSONException + */ + public static String toString(Object object) throws JSONException { + return toString(object, null); + } + + /** + * Convert a JSONObject into a well-formed, element-normal XML string. + * + * @param object A JSONObject. + * @param tagName The optional name of the enclosing tag. + * @return A string. + * @throws JSONException + */ + public static String toString(Object object, String tagName) throws JSONException { + StringBuilder sb = new StringBuilder(); + int i; + JSONArray ja; + int length; + String string; + if (object instanceof JSONObject) { + // Emit + if (tagName != null) { + sb.append('<'); + sb.append(tagName); + sb.append('>'); + } + // Loop thru the keys. + JSONObject jo = (JSONObject) object; + Iterator keys = jo.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object value = jo.opt(key); + if (value == null) { + value = ""; + } + string = value instanceof String ? (String) value : null; + // Emit content in body + if ("content".equals(key)) { + if (value instanceof JSONArray) { + ja = (JSONArray) value; + length = ja.length(); + for (i = 0; i < length; i += 1) { + if (i > 0) { + sb.append('\n'); + } + sb.append(escape(ja.get(i).toString())); + } + } else { + sb.append(escape(value.toString())); + } + // Emit an array of similar keys + } else if (value instanceof JSONArray) { + ja = (JSONArray) value; + length = ja.length(); + for (i = 0; i < length; i += 1) { + value = ja.get(i); + if (value instanceof JSONArray) { + sb.append('<'); + sb.append(key); + sb.append('>'); + sb.append(toString(value)); + sb.append("'); + } else { + sb.append(toString(value, key)); + } + } + } else if ("".equals(value)) { + sb.append('<'); + sb.append(key); + sb.append("/>"); + // Emit a new tag + } else { + sb.append(toString(value, key)); + } + } + if (tagName != null) { + // Emit the close tag + sb.append("'); + } + return sb.toString(); + // XML does not have good support for arrays. If an array appears in + // a place + // where XML is lacking, synthesize an element. + } else { + if (object.getClass().isArray()) { + object = new JSONArray(object); + } + if (object instanceof JSONArray) { + ja = (JSONArray) object; + length = ja.length(); + for (i = 0; i < length; i += 1) { + sb.append(toString(ja.opt(i), tagName == null ? "array" : tagName)); + } + return sb.toString(); + } else { + string = escape(object.toString()); + return (tagName == null) ? + '"' + string + '"' : + string.isEmpty() ? + '<' + tagName + "/>" : + '<' + tagName + '>' + string + "'; + } + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/XMLTokener.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/XMLTokener.java new file mode 100644 index 000000000..96d65541f --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/json/XMLTokener.java @@ -0,0 +1,315 @@ +package com.github.intellectualsites.plotsquared.json; + +import java.util.HashMap; + +/** + * The XMLTokener extends the JSONTokener to provide additional methods for the parsing of XML texts. + * + * @author JSON.org + * @version 2014-05-03 + */ +public class XMLTokener extends JSONTokener { + /** + * The table of entity values. It initially contains Character values for amp, apos, gt, lt, quot. + */ + public static final HashMap entity; + + static { + entity = new HashMap<>(8); + entity.put("amp", XML.AMP); + entity.put("apos", XML.APOS); + entity.put("gt", XML.GT); + entity.put("lt", XML.LT); + entity.put("quot", XML.QUOT); + } + + /** + * Construct an XMLTokener from a string. + * + * @param s A source string. + */ + public XMLTokener(final String s) { + super(s); + } + + /** + * Get the text in the CDATA block. + * + * @return The string up to the ]]>. + * @throws JSONException If the ]]> is not found. + */ + public String nextCDATA() throws JSONException { + final StringBuilder sb = new StringBuilder(); + for (; ; ) { + char c = next(); + if (end()) { + throw syntaxError("Unclosed CDATA"); + } + sb.append(c); + int i = sb.length() - 3; + if ((i >= 0) && (sb.charAt(i) == ']') && (sb.charAt(i + 1) == ']') && (sb.charAt(i + 2) + == '>')) { + sb.setLength(i); + return sb.toString(); + } + } + } + + /** + * Get the next XML outer token, trimming whitespace. There are two kinds of tokens: the '<' character which begins + * a markup tag, and the content text between markup tags. + * + * @return A string, or a '<' Character, or null if there is no more source text. + * @throws JSONException + */ + public Object nextContent() throws JSONException { + char c; + do { + c = next(); + } while (Character.isWhitespace(c)); + if (c == 0) { + return null; + } + if (c == '<') { + return XML.LT; + } + StringBuilder sb = new StringBuilder(); + for (; ; ) { + if ((c == '<') || (c == 0)) { + back(); + return sb.toString().trim(); + } + if (c == '&') { + sb.append(nextEntity('&')); + } else { + sb.append(c); + } + c = next(); + } + } + + /** + * Return the next entity. These entities are translated to Characters: & " > < + * ". + * + * @param ampersand An ampersand character. + * @return A Character or an entity String if the entity is not recognized. + * @throws JSONException If missing ';' in XML entity. + */ + public Object nextEntity(final char ampersand) throws JSONException { + final StringBuilder sb = new StringBuilder(); + for (; ; ) { + final char c = next(); + if (Character.isLetterOrDigit(c) || (c == '#')) { + sb.append(Character.toLowerCase(c)); + } else if (c == ';') { + break; + } else { + throw syntaxError("Missing ';' in XML entity: &" + sb); + } + } + final String string = sb.toString(); + final Object object = entity.get(string); + return object != null ? object : ampersand + string + ';'; + } + + /** + * Returns the next XML meta token. This is used for skipping over and structures. + * + * @return Syntax characters (< > / = ! ?) are returned as Character, and strings and names are + * returned as Boolean. We don't care what the values actually are. + * @throws JSONException If a string is not properly closed or if the XML is badly structured. + */ + public Object nextMeta() throws JSONException { + char c; + do { + c = next(); + } while (Character.isWhitespace(c)); + char q; + switch (c) { + case 0: + throw syntaxError("Misshaped meta tag"); + case '<': + return XML.LT; + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + case '"': + case '\'': + q = c; + for (; ; ) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return Boolean.TRUE; + } + } + default: + for (; ; ) { + c = next(); + if (Character.isWhitespace(c)) { + return Boolean.TRUE; + } + switch (c) { + case 0: + case '<': + case '>': + case '/': + case '=': + case '!': + case '?': + case '"': + case '\'': + back(); + return Boolean.TRUE; + } + } + } + } + + /** + * Get the next XML Token. These tokens are found inside of angle brackets. It may be one of these characters: + * / >= ! ? or it may be a string wrapped in single quotes or double quotes, or it may be a name. + * + * @return a String or a Character. + * @throws JSONException If the XML is not well formed. + */ + public Object nextToken() throws JSONException { + char c; + do { + c = next(); + } while (Character.isWhitespace(c)); + char q; + StringBuilder sb; + switch (c) { + case 0: + throw syntaxError("Misshaped element"); + case '<': + throw syntaxError("Misplaced '<'"); + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + // Quoted string + case '"': + case '\'': + q = c; + sb = new StringBuilder(); + for (; ; ) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return sb.toString(); + } + if (c == '&') { + sb.append(nextEntity('&')); + } else { + sb.append(c); + } + } + default: + // Name + sb = new StringBuilder(); + for (; ; ) { + sb.append(c); + c = next(); + if (Character.isWhitespace(c)) { + return sb.toString(); + } + switch (c) { + case 0: + return sb.toString(); + case '>': + case '/': + case '=': + case '!': + case '?': + case '[': + case ']': + back(); + return sb.toString(); + case '<': + case '"': + case '\'': + throw syntaxError("Bad character in a name"); + } + } + } + } + + /** + * Skip characters until past the requested string. If it is not found, we are left at the end of the source with a + * result of false. + * + * @param to A string to skip past. + * @throws JSONException + */ + public boolean skipPast(final String to) throws JSONException { + char c; + int i; + final int length = to.length(); + final char[] circle = new char[length]; + /* + * First fill the circle buffer with as many characters as are in the + * to string. If we reach an early end, bail. + */ + for (i = 0; i < length; i += 1) { + c = next(); + if (c == 0) { + return false; + } + circle[i] = c; + } + /* We will loop, possibly for all of the remaining characters. */ + for (int offset = 0; ; ) { + int j = offset; + boolean b = true; + /* Compare the circle buffer with the to string. */ + for (i = 0; i < length; i += 1) { + if (circle[j] != to.charAt(i)) { + b = false; + break; + } + j += 1; + if (j >= length) { + j -= length; + } + } + /* If we exit the loop with b intact, then victory is ours. */ + if (b) { + return true; + } + /* Get the next character. If there isn't one, then defeat is ours. */ + c = next(); + if (c == 0) { + return false; + } + /* + * Shove the character in the circle buffer and advance the + * circle offset. The offset is mod n. + */ + circle[offset] = c; + offset += 1; + if (offset >= length) { + offset -= length; + } + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/IPlotMain.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/IPlotMain.java new file mode 100644 index 000000000..2f6559a4e --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/IPlotMain.java @@ -0,0 +1,285 @@ +package com.github.intellectualsites.plotsquared.plot; + +import com.github.intellectualsites.plotsquared.plot.generator.GeneratorWrapper; +import com.github.intellectualsites.plotsquared.plot.generator.HybridUtils; +import com.github.intellectualsites.plotsquared.plot.generator.IndependentPlotGenerator; +import com.github.intellectualsites.plotsquared.plot.logger.ILogger; +import com.github.intellectualsites.plotsquared.plot.object.BlockRegistry; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.*; +import com.github.intellectualsites.plotsquared.plot.util.block.QueueProvider; + +import java.io.File; +import java.util.List; + +public interface IPlotMain extends ILogger { + + /** + * Log a message to console. + * + * @param message The message to log + */ + void log(String message); + + /** + * Get the `PlotSquared` directory. + * + * @return The plugin directory + */ + File getDirectory(); + + /** + * Get the directory containing all the worlds. + * + * @return The directory containing the worlds + */ + File getWorldContainer(); + + /** + * Wrap a player into a PlotPlayer object. + * + * @param player The player to convert to a PlotPlayer + * @return A PlotPlayer + */ + PlotPlayer wrapPlayer(Object player); + + /** + * Disable the implementation. + * + *

    + *
  • If a full disable isn't feasibly, just disable what it can. + *
+ */ + void disable(); + + /** + * Completely shut down the plugin + */ + void shutdown(); + + /** + * Get the version of the PlotSquared being used. + * + * @return the plugin version + */ + int[] getPluginVersion(); + + /** + * Get the version of the PlotSquared being used as a string. + * + * @return the plugin version as a string + */ + String getPluginVersionString(); + + /** + * Usually PlotSquared + * + * @return + */ + default String getPluginName() { + return "PlotSquared"; + } + + /** + * Get the version of Minecraft that is running. + * + * @return + */ + int[] getServerVersion(); + + /** + * Get the server implementation name and version + */ + String getServerImplementation(); + + /** + * Get the NMS package prefix. + * + * @return The NMS package prefix + */ + String getNMSPackage(); + + /** + * Get the schematic handler. + * + * @return The {@link SchematicHandler} + */ + SchematicHandler initSchematicHandler(); + + /** + * Get the Chat Manager. + * + * @return The {@link ChatManager} + */ + ChatManager initChatManager(); + + /** + * The task manager will run and manage Minecraft tasks. + * + * @return + */ + TaskManager getTaskManager(); + + /** + * Run the task that will kill road mobs. + */ + void runEntityTask(); + + /** + * Register the implementation specific commands. + */ + void registerCommands(); + + /** + * Register the protection system. + */ + void registerPlayerEvents(); + + /** + * Register inventory related events. + */ + void registerInventoryEvents(); + + /** + * Register plot plus related events. + */ + void registerPlotPlusEvents(); + + /** + * Register force field events. + */ + void registerForceFieldEvents(); + + /** + * Register the WorldEdit hook. + */ + boolean initWorldEdit(); + + /** + * Get the economy provider. + * + * @return + */ + EconHandler getEconomyHandler(); + + /** + * Get the {@link QueueProvider} class. + * + * @return + */ + QueueProvider initBlockQueue(); + + /** + * Get the {@link WorldUtil} class. + * + * @return + */ + WorldUtil initWorldUtil(); + + /** + * Get the EventUtil class. + * + * @return + */ + EventUtil initEventUtil(); + + /** + * Get the chunk manager. + * + * @return + */ + ChunkManager initChunkManager(); + + /** + * Get the {@link SetupUtils} class. + * + * @return + */ + SetupUtils initSetupUtils(); + + /** + * Get {@link HybridUtils} class. + * + * @return + */ + HybridUtils initHybridUtils(); + + /** + * Start Metrics. + */ + void startMetrics(); + + /** + * If a world is already loaded, set the generator (use NMS if required). + * + * @param world The world to set the generator + */ + void setGenerator(String world); + + /** + * Get the {@link UUIDHandlerImplementation} which will cache and + * provide UUIDs. + * + * @return + */ + UUIDHandlerImplementation initUUIDHandler(); + + /** + * Get the {@link InventoryUtil} class (used for implementation specific + * inventory guis). + * + * @return + */ + InventoryUtil initInventoryUtil(); + + /** + * Unregister a PlotPlayer from cache e.g. if they have logged off. + * + * @param player + */ + void unregister(PlotPlayer player); + + /** + * Get the generator wrapper for a world (world) and generator (name). + * + * @param world + * @param name + * @return + */ + GeneratorWrapper getGenerator(String world, String name); + + GeneratorWrapper wrapPlotGenerator(String world, IndependentPlotGenerator generator); + + /** + * Register the chunk processor which will clean out chunks that have too + * many blockstates or entities. + */ + void registerChunkProcessor(); + + /** + * Register the world initialization events (used to keep track of worlds + * being generated). + */ + void registerWorldEvents(); + + /** + * Usually HybridGen + * + * @return Default implementation generator + */ + IndependentPlotGenerator getDefaultGenerator(); + + /** + * Get the class that will manage player titles. + * + * @return + */ + AbstractTitle initTitleManager(); + + List getPluginIds(); + + BlockRegistry getBlockRegistry(); + + LegacyMappings getLegacyMappings(); + +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/Platform.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/Platform.java new file mode 100644 index 000000000..9dfbf2ffb --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/Platform.java @@ -0,0 +1,6 @@ +package com.github.intellectualsites.plotsquared.plot; + +public enum Platform { + Bukkit, Sponge, Spigot + +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/PlotSquared.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/PlotSquared.java new file mode 100644 index 000000000..63bbf8142 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/PlotSquared.java @@ -0,0 +1,2066 @@ +package com.github.intellectualsites.plotsquared.plot; + +import com.github.intellectualsites.plotsquared.configuration.ConfigurationSection; +import com.github.intellectualsites.plotsquared.configuration.MemorySection; +import com.github.intellectualsites.plotsquared.configuration.file.YamlConfiguration; +import com.github.intellectualsites.plotsquared.configuration.serialization.ConfigurationSerialization; +import com.github.intellectualsites.plotsquared.plot.commands.WE_Anywhere; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Configuration; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.config.Storage; +import com.github.intellectualsites.plotsquared.plot.database.*; +import com.github.intellectualsites.plotsquared.plot.generator.GeneratorWrapper; +import com.github.intellectualsites.plotsquared.plot.generator.HybridPlotWorld; +import com.github.intellectualsites.plotsquared.plot.generator.HybridUtils; +import com.github.intellectualsites.plotsquared.plot.generator.IndependentPlotGenerator; +import com.github.intellectualsites.plotsquared.plot.listener.WESubscriber; +import com.github.intellectualsites.plotsquared.plot.logger.ILogger; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.object.worlds.DefaultPlotAreaManager; +import com.github.intellectualsites.plotsquared.plot.object.worlds.PlotAreaManager; +import com.github.intellectualsites.plotsquared.plot.object.worlds.SinglePlotArea; +import com.github.intellectualsites.plotsquared.plot.object.worlds.SinglePlotAreaManager; +import com.github.intellectualsites.plotsquared.plot.util.*; +import com.github.intellectualsites.plotsquared.plot.util.block.GlobalBlockQueue; +import com.github.intellectualsites.plotsquared.plot.util.expiry.ExpireManager; +import com.github.intellectualsites.plotsquared.plot.util.expiry.ExpiryTask; +import com.sk89q.worldedit.WorldEdit; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; + +import javax.annotation.Nullable; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.sql.SQLException; +import java.util.*; +import java.util.Map.Entry; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * An implementation of the core, with a static getter for easy access. + */ +@SuppressWarnings({"unused", "WeakerAccess"}) public class PlotSquared { + + private static final Set EMPTY_SET = Collections.unmodifiableSet(Collections.emptySet()); + private static PlotSquared instance; + // Implementation + public final IPlotMain IMP; + // Current thread + private final Thread thread; + // WorldEdit instance + public WorldEdit worldedit; + public File styleFile; + public File configFile; + public File worldsFile; + public File commandsFile; + public File translationFile; + public YamlConfiguration style; + public YamlConfiguration config; + public YamlConfiguration worlds; + public YamlConfiguration storage; + public YamlConfiguration commands; + // Temporary hold the plots/clusters before the worlds load + public HashMap> clusters_tmp; + public HashMap> plots_tmp; + // Implementation logger + @Setter @Getter private ILogger logger; + // Platform / Version / Update URL + private PlotVersion version; + // Files and configuration + @Getter private File jarFile = null; // This file + private File storageFile; + @Getter private PlotAreaManager plotAreaManager; + + /** + * Initialize PlotSquared with the desired Implementation class. + * + * @param iPlotMain Implementation of {@link IPlotMain} used + * @param platform The platform being used + */ + public PlotSquared(final IPlotMain iPlotMain, final String platform) { + if (instance != null) { + throw new IllegalStateException("Cannot re-initialize the PlotSquared singleton"); + } + instance = this; + + this.thread = Thread.currentThread(); + this.IMP = iPlotMain; + this.logger = iPlotMain; + Settings.PLATFORM = platform; + + // + // Register configuration serializable classes + // + ConfigurationSerialization.registerClass(PlotBlock.class, "PlotBlock"); + ConfigurationSerialization.registerClass(BlockBucket.class, "BlockBucket"); + + try { + new ReflectionUtils(this.IMP.getNMSPackage()); + try { + URL url = PlotSquared.class.getProtectionDomain().getCodeSource().getLocation(); + this.jarFile = new File( + new URL(url.toURI().toString().split("\\!")[0].replaceAll("jar:file", "file")) + .toURI().getPath()); + } catch (MalformedURLException | URISyntaxException | SecurityException e) { + e.printStackTrace(); + this.jarFile = new File(this.IMP.getDirectory().getParentFile(), "PlotSquared.jar"); + if (!this.jarFile.exists()) { + this.jarFile = new File(this.IMP.getDirectory().getParentFile(), + "PlotSquared-" + platform + ".jar"); + } + } + TaskManager.IMP = this.IMP.getTaskManager(); + + // World Util. Has to be done before config files are loaded + WorldUtil.IMP = this.IMP.initWorldUtil(); + + if (!setupConfigs()) { + return; + } + this.translationFile = MainUtil.getFile(this.IMP.getDirectory(), + Settings.Paths.TRANSLATIONS + File.separator + IMP.getPluginName() + + ".use_THIS.yml"); + C.load(this.translationFile); + + // Setup plotAreaManager + if (Settings.Enabled_Components.WORLDS) { + this.plotAreaManager = new SinglePlotAreaManager(); + } else { + this.plotAreaManager = new DefaultPlotAreaManager(); + } + + // Database + if (Settings.Enabled_Components.DATABASE) { + setupDatabase(); + } + // Comments + CommentManager.registerDefaultInboxes(); + // Kill entities + if (Settings.Enabled_Components.KILL_ROAD_MOBS + || Settings.Enabled_Components.KILL_ROAD_VEHICLES) { + this.IMP.runEntityTask(); + } + if (Settings.Enabled_Components.EVENTS) { + this.IMP.registerPlayerEvents(); + this.IMP.registerInventoryEvents(); + this.IMP.registerPlotPlusEvents(); + } + // Required + this.IMP.registerWorldEvents(); + if (Settings.Enabled_Components.METRICS) { + this.IMP.startMetrics(); + } else { + PlotSquared.log(C.CONSOLE_PLEASE_ENABLE_METRICS.f(IMP.getPluginName())); + } + if (Settings.Enabled_Components.CHUNK_PROCESSOR) { + this.IMP.registerChunkProcessor(); + } + // create UUIDWrapper + UUIDHandler.implementation = this.IMP.initUUIDHandler(); + if (Settings.Enabled_Components.UUID_CACHE) { + startUuidCatching(); + } else { + // Start these separately + UUIDHandler.add(new StringWrapper("*"), DBFunc.EVERYONE); + startExpiryTasks(); + } + // create event util class + EventUtil.manager = this.IMP.initEventUtil(); + // create Hybrid utility class + HybridUtils.manager = this.IMP.initHybridUtils(); + // Inventory utility class + InventoryUtil.manager = this.IMP.initInventoryUtil(); + // create setup util class + SetupUtils.manager = this.IMP.initSetupUtils(); + // Set block + GlobalBlockQueue.IMP = new GlobalBlockQueue(IMP.initBlockQueue(), 1); + GlobalBlockQueue.IMP.runTask(); + // Set chunk + ChunkManager.manager = this.IMP.initChunkManager(); + // Schematic handler + SchematicHandler.manager = this.IMP.initSchematicHandler(); + // Titles + AbstractTitle.TITLE_CLASS = this.IMP.initTitleManager(); + // Chat + ChatManager.manager = this.IMP.initChatManager(); + // Commands + if (Settings.Enabled_Components.COMMANDS) { + this.IMP.registerCommands(); + } + // WorldEdit + if (Settings.Enabled_Components.WORLDEDIT_RESTRICTIONS) { + try { + if (this.IMP.initWorldEdit()) { + PlotSquared.debug(IMP.getPluginName() + " hooked into WorldEdit."); + this.worldedit = WorldEdit.getInstance(); + WorldEdit.getInstance().getEventBus().register(new WESubscriber()); + if (Settings.Enabled_Components.COMMANDS) { + new WE_Anywhere(); + } + + } + } catch (Throwable e) { + PlotSquared.debug( + "Incompatible version of WorldEdit, please upgrade: http://builds.enginehub.org/job/worldedit?branch=master"); + } + } + // Economy + if (Settings.Enabled_Components.ECONOMY) { + TaskManager + .runTask(() -> EconHandler.manager = PlotSquared.this.IMP.getEconomyHandler()); + } + +/* // Check for updates + if (Settings.Enabled_Components.UPDATER) { + updater = new Updater(); + TaskManager.IMP.taskAsync(new Runnable() { + @Override public void run() { + updater.update(getPlatform(), getVersion()); + } + }); + TaskManager.IMP.taskRepeatAsync(new Runnable() { + @Override public void run() { + updater.update(getPlatform(), getVersion()); + } + }, 36000); + }*/ + + // World generators: + final ConfigurationSection section = this.worlds.getConfigurationSection("worlds"); + if (section != null) { + for (String world : section.getKeys(false)) { + if (world.equals("CheckingPlotSquaredGenerator")) { + continue; + } + if (WorldUtil.IMP.isWorld(world)) { + this.IMP.setGenerator(world); + } + } + TaskManager.runTaskLater(() -> { + for (String world : section.getKeys(false)) { + if (world.equals("CheckingPlotSquaredGenerator")) { + continue; + } + if (!WorldUtil.IMP.isWorld(world) && !world.equals("*")) { + debug( + "&c`" + world + "` was not properly loaded - " + IMP.getPluginName() + + " will now try to load it properly: "); + debug( + "&8 - &7Are you trying to delete this world? Remember to remove it from the settings.yml, bukkit.yml and multiverse worlds.yml"); + debug( + "&8 - &7Your world management plugin may be faulty (or non existent)"); + PlotSquared.this.IMP.setGenerator(world); + } + } + }, 1); + } + + // Copy files + copyFile("addplots.js", Settings.Paths.SCRIPTS); + copyFile("addsigns.js", Settings.Paths.SCRIPTS); + copyFile("automerge.js", Settings.Paths.SCRIPTS); + copyFile("furthest.js", Settings.Paths.SCRIPTS); + copyFile("mycommand.js", Settings.Paths.SCRIPTS); + copyFile("setbiomes.js", Settings.Paths.SCRIPTS); + copyFile("start.js", Settings.Paths.SCRIPTS); + copyFile("town.template", Settings.Paths.TEMPLATES); + copyFile("skyblock.template", Settings.Paths.TEMPLATES); + copyFile("bridge.template", Settings.Paths.TEMPLATES); + copyFile("de-DE.yml", Settings.Paths.TRANSLATIONS); + copyFile("es-ES.yml", Settings.Paths.TRANSLATIONS); + copyFile("zh-CN.yml", Settings.Paths.TRANSLATIONS); + copyFile("it-IT.yml", Settings.Paths.TRANSLATIONS); + copyFile("ko-KR.yml", Settings.Paths.TRANSLATIONS); + copyFile("fr-FR.yml", Settings.Paths.TRANSLATIONS); + showDebug(); + } catch (Throwable e) { + e.printStackTrace(); + } + + PlotSquared.log(C.ENABLED.f(IMP.getPluginName())); + } + + /** + * Get an instance of PlotSquared. + * + * @return instance of PlotSquared + */ + public static PlotSquared get() { + return PlotSquared.instance; + } + + public static IPlotMain imp() { + if (instance != null) { + return instance.IMP; + } + return null; + } + + /** + * Log a message to the IPlotMain logger. + * + * @param message Message to log + * @see IPlotMain#log(String) + */ + public static void log(Object message) { + if (message == null || message.toString().isEmpty()) { + return; + } + if (PlotSquared.get() == null || PlotSquared.get().getLogger() == null) { + System.out.printf("[P2][Info] %s\n", StringMan.getString(message)); + } else { + PlotSquared.get().getLogger().log(StringMan.getString(message)); + } + } + + /** + * Log a message to the IPlotMain logger. + * + * @param message Message to log + * @see IPlotMain#log(String) + */ + public static void debug(@Nullable Object message) { + if (Settings.DEBUG) { + if (PlotSquared.get() == null || PlotSquared.get().getLogger() == null) { + System.out.printf("[P2][Debug] %s\n", StringMan.getString(message)); + } else { + PlotSquared.get().getLogger().log(StringMan.getString(message)); + } + } + } + + private void startUuidCatching() { + TaskManager.runTaskLater(() -> { + debug("Starting UUID caching"); + UUIDHandler.startCaching(() -> { + UUIDHandler.add(new StringWrapper("*"), DBFunc.EVERYONE); + foreachPlotRaw(new RunnableVal() { + @Override public void run(Plot plot) { + if (plot.hasOwner() && plot.temp != -1) { + if (UUIDHandler.getName(plot.owner) == null) { + UUIDHandler.implementation.unknown.add(plot.owner); + } + } + } + }); + startExpiryTasks(); + }); + }, 20); + } + + private void startExpiryTasks() { + if (Settings.Enabled_Components.PLOT_EXPIRY) { + ExpireManager.IMP = new ExpireManager(); + ExpireManager.IMP.runAutomatedTask(); + for (Settings.Auto_Clear settings : Settings.AUTO_CLEAR.getInstances()) { + ExpiryTask task = new ExpiryTask(settings); + ExpireManager.IMP.addTask(task); + } + } + } + + public boolean isMainThread(Thread thread) { + return this.thread == thread; + } + + /** + * Check if `version` is >= `version2`. + * + * @param version First version + * @param version2 Second version + * @return true if `version` is >= `version2` + */ + public boolean checkVersion(int[] version, int... version2) { + return version[0] > version2[0] || version[0] == version2[0] && version[1] > version2[1] + || version[0] == version2[0] && version[1] == version2[1] && version[2] >= version2[2]; + } + + /** + * Get the current PlotSquared version. + * + * @return current version in config or null + */ + public PlotVersion getVersion() { + return this.version; + } + + /** + * Get the server platform this plugin is running on this is running on. + *

+ *

This will be either Bukkit or Sponge

+ * + * @return the server implementation + */ + public String getPlatform() { + return Settings.PLATFORM; + } + + public PlotManager getPlotManager(Plot plot) { + return plot.getArea().manager; + } + + public PlotManager getPlotManager(Location location) { + PlotArea pa = getPlotAreaAbs(location); + return pa != null ? pa.manager : null; + } + + /** + * Add a global reference to a plot world. + * + * @param plotArea the {@code PlotArea} to add. + * @see #removePlotArea(PlotArea) To remove the reference + */ + public void addPlotArea(PlotArea plotArea) { + HashMap plots; + if (plots_tmp == null || (plots = plots_tmp.remove(plotArea.toString())) == null) { + if (plotArea.TYPE == 2) { + plots = this.plots_tmp != null ? this.plots_tmp.get(plotArea.worldname) : null; + if (plots != null) { + Iterator> iterator = plots.entrySet().iterator(); + while (iterator.hasNext()) { + Entry next = iterator.next(); + PlotId id = next.getKey(); + if (plotArea.contains(id)) { + next.getValue().setArea(plotArea); + iterator.remove(); + } + } + } + } + } else { + for (Plot entry : plots.values()) { + entry.setArea(plotArea); + } + } + Set clusters; + if (clusters_tmp == null || (clusters = clusters_tmp.remove(plotArea.toString())) == null) { + if (plotArea.TYPE == 2) { + clusters = + this.clusters_tmp != null ? this.clusters_tmp.get(plotArea.worldname) : null; + if (clusters != null) { + Iterator iterator = clusters.iterator(); + while (iterator.hasNext()) { + PlotCluster next = iterator.next(); + if (next.intersects(plotArea.getMin(), plotArea.getMax())) { + next.setArea(plotArea); + iterator.remove(); + } + } + } + } + } else { + for (PlotCluster cluster : clusters) { + cluster.setArea(plotArea); + } + } + plotAreaManager.addPlotArea(plotArea); + plotArea.setupBorder(); + } + + /** + * Remove a plot world reference. + * + * @param area the {@code PlotArea} to remove + */ + public void removePlotArea(PlotArea area) { + plotAreaManager.removePlotArea(area); + setPlotsTmp(area); + } + + public void removePlotAreas(String world) { + for (PlotArea area : getPlotAreas(world)) { + if (area.worldname.equals(world)) { + removePlotArea(area); + } + } + } + + private void setPlotsTmp(PlotArea area) { + if (this.plots_tmp == null) { + this.plots_tmp = new HashMap<>(); + } + HashMap map = + this.plots_tmp.computeIfAbsent(area.toString(), k -> new HashMap<>()); + for (Plot plot : area.getPlots()) { + map.put(plot.getId(), plot); + } + if (this.clusters_tmp == null) { + this.clusters_tmp = new HashMap<>(); + } + this.clusters_tmp.put(area.toString(), area.getClusters()); + } + + public Set getClusters(String world) { + Set set = new HashSet<>(); + for (PlotArea area : getPlotAreas(world)) { + set.addAll(area.getClusters()); + } + return Collections.unmodifiableSet(set); + + } + + /** + * Get all the base plots in a single set (for merged plots it just returns the bottom plot). + * + * @return Set of base Plots + */ + public Set getBasePlots() { + int size = getPlotCount(); + final Set result = new HashSet<>(size); + foreachPlotArea(new RunnableVal() { + @Override public void run(PlotArea value) { + for (Plot plot : value.getPlots()) { + if (!plot.isBasePlot()) { + continue; + } + result.add(plot); + } + } + }); + return Collections.unmodifiableSet(result); + } + + public List sortPlotsByTemp(Collection plots) { + int max = 0; + int overflowCount = 0; + for (Plot plot : plots) { + if (plot.temp > 0) { + if (plot.temp > max) { + max = plot.temp; + } + } else { + overflowCount++; + } + } + Plot[] array = new Plot[max + 1]; + List overflow = new ArrayList<>(overflowCount); + for (Plot plot : plots) { + if (plot.temp <= 0) { + overflow.add(plot); + } else { + array[plot.temp] = plot; + } + } + ArrayList result = new ArrayList<>(plots.size()); + for (Plot plot : array) { + if (plot != null) { + result.add(plot); + } + } + overflow.sort(Comparator.comparingInt(Plot::hashCode)); + result.addAll(overflow); + return result; + } + + /** + * Sort plots by hashcode. + * + * @param plots the collection of plots to sort + * @return the sorted collection {@link #sortPlots(Collection, SortType, PlotArea)} which has + * additional checks before calling this + */ + // TODO: Re-evaluate this (previously public and deprecated), as it's being used internally + private ArrayList sortPlotsByHash(Collection plots) { + int hardmax = 256000; + int max = 0; + int overflowSize = 0; + for (Plot plot : plots) { + int hash = MathMan.getPositiveId(plot.hashCode()); + if (hash > max) { + if (hash >= hardmax) { + overflowSize++; + } else { + max = hash; + } + } + } + hardmax = Math.min(hardmax, max); + Plot[] cache = new Plot[hardmax + 1]; + List overflow = new ArrayList<>(overflowSize); + ArrayList extra = new ArrayList<>(); + for (Plot plot : plots) { + int hash = MathMan.getPositiveId(plot.hashCode()); + if (hash < hardmax) { + if (hash >= 0) { + cache[hash] = plot; + } else { + extra.add(plot); + } + } else if (Math.abs(plot.getId().x) > 15446 || Math.abs(plot.getId().y) > 15446) { + extra.add(plot); + } else { + overflow.add(plot); + } + } + Plot[] overflowArray = overflow.toArray(new Plot[overflow.size()]); + sortPlotsByHash(overflowArray); + ArrayList result = new ArrayList<>(cache.length + overflowArray.length); + for (Plot plot : cache) { + if (plot != null) { + result.add(plot); + } + } + Collections.addAll(result, overflowArray); + result.addAll(extra); + return result; + } + + private void sortPlotsByHash(Plot[] input) { + List[] bucket = new ArrayList[32]; + for (int i = 0; i < bucket.length; i++) { + bucket[i] = new ArrayList<>(); + } + boolean maxLength = false; + int placement = 1; + while (!maxLength) { + maxLength = true; + for (Plot i : input) { + int tmp = MathMan.getPositiveId(i.hashCode()) / placement; + bucket[tmp & 31].add(i); + if (maxLength && tmp > 0) { + maxLength = false; + } + } + int a = 0; + for (int b = 0; b < 32; b++) { + for (Plot i : bucket[b]) { + input[a++] = i; + } + bucket[b].clear(); + } + placement *= 32; + } + } + + private ArrayList sortPlotsByTimestamp(Collection plots) { + int hardMax = 256000; + int max = 0; + int overflowSize = 0; + for (Plot plot : plots) { + int hash = MathMan.getPositiveId(plot.hashCode()); + if (hash > max) { + if (hash >= hardMax) { + overflowSize++; + } else { + max = hash; + } + } + } + hardMax = Math.min(hardMax, max); + Plot[] cache = new Plot[hardMax + 1]; + List overflow = new ArrayList<>(overflowSize); + ArrayList extra = new ArrayList<>(); + for (Plot plot : plots) { + int hash = MathMan.getPositiveId(plot.hashCode()); + if (hash < hardMax) { + if (hash >= 0) { + cache[hash] = plot; + } else { + extra.add(plot); + } + } else if (Math.abs(plot.getId().x) > 15446 || Math.abs(plot.getId().y) > 15446) { + extra.add(plot); + } else { + overflow.add(plot); + } + } + Plot[] overflowArray = overflow.toArray(new Plot[overflow.size()]); + sortPlotsByHash(overflowArray); + ArrayList result = new ArrayList<>(cache.length + overflowArray.length); + for (Plot plot : cache) { + if (plot != null) { + result.add(plot); + } + } + Collections.addAll(result, overflowArray); + result.addAll(extra); + return result; + } + + /** + * Sort plots by creation timestamp. + */ + + private List sortPlotsByModified(Collection input) { + List list; + if (input instanceof List) { + list = (List) input; + } else { + list = new ArrayList<>(input); + } + list.sort(Comparator.comparingLong(a -> ExpireManager.IMP.getTimestamp(a.owner))); + return list; + } + + /** + * Sort a collection of plots by world (with a priority world), then by hashcode. + * + * @param plots the plots to sort + * @param type The sorting method to use for each world (timestamp, or hash) + * @param priorityArea Use null, "world", or "gibberish" if you want default world order + * @return ArrayList of plot + */ + public ArrayList sortPlots(Collection plots, SortType type, + final PlotArea priorityArea) { + // group by world + // sort each + HashMap> map = new HashMap<>(); + int totalSize = getPlotCount(); + if (plots.size() == totalSize) { + for (PlotArea area : plotAreaManager.getAllPlotAreas()) { + map.put(area, area.getPlots()); + } + } else { + for (PlotArea area : plotAreaManager.getAllPlotAreas()) { + map.put(area, new ArrayList<>(0)); + } + Collection lastList = null; + PlotArea lastWorld = null; + for (Plot plot : plots) { + if (lastWorld == plot.getArea()) { + lastList.add(plot); + } else { + lastWorld = plot.getArea(); + lastList = map.get(lastWorld); + lastList.add(plot); + } + } + } + List areas = Arrays.asList(plotAreaManager.getAllPlotAreas()); + areas.sort((a, b) -> { + if (priorityArea != null) { + if (a.equals(priorityArea)) { + return -1; + } else if (b.equals(priorityArea)) { + return 1; + } + } + return a.hashCode() - b.hashCode(); + }); + ArrayList toReturn = new ArrayList<>(plots.size()); + for (PlotArea area : areas) { + switch (type) { + case CREATION_DATE: + toReturn.addAll(sortPlotsByTemp(map.get(area))); + break; + case CREATION_DATE_TIMESTAMP: + toReturn.addAll(sortPlotsByTimestamp(map.get(area))); + break; + case DISTANCE_FROM_ORIGIN: + toReturn.addAll(sortPlotsByHash(map.get(area))); + break; + case LAST_MODIFIED: + toReturn.addAll(sortPlotsByModified(map.get(area))); + break; + default: + break; + } + } + return toReturn; + } + + /** + * A more generic way to filter plots - make your own method if you need complex filters. + * + * @param filters the filter + * @return a filtered set of plots + */ + public Set getPlots(final PlotFilter... filters) { + final HashSet set = new HashSet<>(); + foreachPlotArea(new RunnableVal() { + @Override public void run(PlotArea value) { + for (PlotFilter filter : filters) { + if (!filter.allowsArea(value)) { + return; + } + } + loop: + for (Entry entry2 : value.getPlotEntries()) { + Plot plot = entry2.getValue(); + for (PlotFilter filter : filters) { + if (!filter.allowsPlot(plot)) { + continue loop; + } + } + set.add(plot); + } + } + }); + return set; + } + + /** + * Get all the plots in a single set. + * + * @return Set of Plots + */ + public Set getPlots() { + int size = getPlotCount(); + final Set result = new HashSet<>(size); + foreachPlotArea(new RunnableVal() { + @Override public void run(PlotArea value) { + result.addAll(value.getPlots()); + } + }); + return result; + } + + //TODO look at uncommenting or deleting tis. Is it useful and will it work? + //If it is readded make sure there is proper javadoc documentation for it. +/* + public void setPlots(HashMap> plots) { + if (this.plots_tmp == null) { + this.plots_tmp = new HashMap<>(); + } + for (Entry> entry : plots.entrySet()) { + String world = entry.getKey(); + PlotArea area = getPlotArea(world, null); + if (area == null) { + HashMap map = this.plots_tmp + .computeIfAbsent(world, k -> new HashMap<>()); + map.putAll(entry.getValue()); + } else { + for (Plot plot : entry.getValue().values()) { + plot.setArea(area); + area.addPlot(plot); + } + } + } + } +*/ + + /** + * Get all the plots owned by a player name. + * + * @param world the world + * @param player the plot owner + * @return Set of Plot + */ + public Set getPlots(String world, String player) { + final UUID uuid = UUIDHandler.getUUID(player, null); + return getPlots(world, uuid); + } + + /** + * Get all the plots owned by a player name. + * + * @param area the PlotArea + * @param player the plot owner + * @return Set of Plot + */ + public Set getPlots(PlotArea area, String player) { + UUID uuid = UUIDHandler.getUUID(player, null); + return getPlots(area, uuid); + } + + /** + * Get all plots by a PlotPlayer. + * + * @param world the world + * @param player the plot owner + * @return Set of plot + */ + public Set getPlots(String world, PlotPlayer player) { + return getPlots(world, player.getUUID()); + } + + /** + * Get all plots by a PlotPlayer. + * + * @param area the PlotArea + * @param player the plot owner + * @return Set of plot + */ + public Set getPlots(PlotArea area, PlotPlayer player) { + return getPlots(area, player.getUUID()); + } + + /** + * Get all plots by a UUID in a world. + * + * @param world the world + * @param uuid the plot owner + * @return Set of plot + */ + public Set getPlots(String world, UUID uuid) { + final Set plots = new HashSet<>(); + for (final Plot plot : getPlots(world)) { + if (plot.hasOwner() && plot.isOwnerAbs(uuid)) { + plots.add(plot); + } + } + return Collections.unmodifiableSet(plots); + } + + /** + * Get all plots by a UUID in an area. + * + * @param area the {@code PlotArea} + * @param uuid the plot owner + * @return Set of plots + */ + public Set getPlots(PlotArea area, UUID uuid) { + final Set plots = new HashSet<>(); + for (Plot plot : getPlots(area)) { + if (plot.hasOwner() && plot.isOwnerAbs(uuid)) { + plots.add(plot); + } + } + return Collections.unmodifiableSet(plots); + } + + /** + * Check if a plot world. + * + * @param world the world + * @return if a plot world is registered + * @see #getPlotAreaByString(String) to get the PlotArea object + */ + public boolean hasPlotArea(String world) { + return plotAreaManager.getPlotAreas(world, null).length != 0; + } + + public Collection getPlots(String world) { + final Set set = new HashSet<>(); + foreachPlotArea(world, new RunnableVal() { + @Override public void run(PlotArea value) { + set.addAll(value.getPlots()); + } + }); + return set; + } + + /** + * Get the plots for a PlotPlayer. + * + * @param player the player to retrieve the plots for + * @return Set of Plot + */ + public Set getPlots(PlotPlayer player) { + return getPlots(player.getUUID()); + } + + public Collection getPlots(PlotArea area) { + return area == null ? EMPTY_SET : area.getPlots(); + } + + public Plot getPlot(PlotArea area, PlotId id) { + return area == null ? null : id == null ? null : area.getPlot(id); + } + + public Set getBasePlots(PlotPlayer player) { + return getBasePlots(player.getUUID()); + } + + /** + * Get the plots for a UUID. + * + * @param uuid the plot owner + * @return Set of Plot's owned by the player + */ + public Set getPlots(final UUID uuid) { + final Set plots = new HashSet<>(); + foreachPlot(new RunnableVal() { + @Override public void run(Plot value) { + if (value.isOwnerAbs(uuid)) { + plots.add(value); + } + } + }); + return Collections.unmodifiableSet(plots); + } + + public boolean hasPlot(final UUID uuid) { + for (final PlotArea area : plotAreaManager.getAllPlotAreas()) { + if (area.hasPlot(uuid)) { + return true; + } + } + return false; + } + + public Set getBasePlots(final UUID uuid) { + final Set plots = new HashSet<>(); + foreachBasePlot(new RunnableVal() { + @Override public void run(Plot value) { + if (value.isOwner(uuid)) { + plots.add(value); + } + } + }); + return Collections.unmodifiableSet(plots); + } + + /** + * Get the plots for a UUID. + * + * @param uuid the UUID of the owner + * @return Set of Plot + */ + public Set getPlotsAbs(final UUID uuid) { + final Set plots = new HashSet<>(); + foreachPlot(new RunnableVal() { + @Override public void run(Plot value) { + if (value.isOwnerAbs(uuid)) { + plots.add(value); + } + } + }); + return Collections.unmodifiableSet(plots); + } + + /** + * Unregister a plot from local memory (does not call DB). + * + * @param plot the plot to remove + * @param callEvent If to call an event about the plot being removed + * @return true if plot existed | false if it didn't + */ + public boolean removePlot(Plot plot, boolean callEvent) { + if (plot == null) { + return false; + } + if (callEvent) { + EventUtil.manager.callDelete(plot); + } + if (plot.getArea().removePlot(plot.getId())) { + PlotId last = (PlotId) plot.getArea().getMeta("lastPlot"); + int last_max = Math.max(Math.abs(last.x), Math.abs(last.y)); + int this_max = Math.max(Math.abs(plot.getId().x), Math.abs(plot.getId().y)); + if (this_max < last_max) { + plot.getArea().setMeta("lastPlot", plot.getId()); + } + return true; + } + return false; + } + + /** + * This method is called by the PlotGenerator class normally. + *
    + *
  • Initializes the PlotArea and PlotManager classes + *
  • Registers the PlotArea and PlotManager classes + *
  • Loads (and/or generates) the PlotArea configuration + *
  • Sets up the world border if configured + *
+ * + *

If loading an augmented plot world: + *

    + *
  • Creates the AugmentedPopulator classes + *
  • Injects the AugmentedPopulator classes if required + *
+ * + * @param world the world to load + * @param baseGenerator The generator for that world, or null + */ + public void loadWorld(String world, GeneratorWrapper baseGenerator) { + if (world.equals("CheckingPlotSquaredGenerator")) { + return; + } + this.plotAreaManager.addWorld(world); + Set worlds; + if (this.worlds.contains("worlds")) { + worlds = this.worlds.getConfigurationSection("worlds").getKeys(false); + } else { + worlds = new HashSet<>(); + } + String path = "worlds." + world; + ConfigurationSection worldSection = this.worlds.getConfigurationSection(path); + int type; + if (worldSection != null) { + type = worldSection.getInt("generator.type", 0); + } else { + type = 0; + } + if (type == 0) { + if (plotAreaManager.getPlotAreas(world, null).length != 0) { + debug("World possibly already loaded: " + world); + return; + } + IndependentPlotGenerator plotGenerator; + if (baseGenerator != null && baseGenerator.isFull()) { + plotGenerator = baseGenerator.getPlotGenerator(); + } else if (worldSection != null) { + String secondaryGeneratorName = worldSection.getString("generator.plugin"); + GeneratorWrapper secondaryGenerator = + this.IMP.getGenerator(world, secondaryGeneratorName); + if (secondaryGenerator != null && secondaryGenerator.isFull()) { + plotGenerator = secondaryGenerator.getPlotGenerator(); + } else { + String primaryGeneratorName = worldSection.getString("generator.init"); + GeneratorWrapper primaryGenerator = + this.IMP.getGenerator(world, primaryGeneratorName); + if (primaryGenerator != null && primaryGenerator.isFull()) { + plotGenerator = primaryGenerator.getPlotGenerator(); + } else { + return; + } + } + } else { + return; + } + // Conventional plot generator + PlotArea plotArea = plotGenerator.getNewPlotArea(world, null, null, null); + PlotManager plotManager = plotGenerator.getNewPlotManager(); + PlotSquared.log(C.PREFIX + "&aDetected world load for '" + world + "'"); + PlotSquared.log(C.PREFIX + "&3 - generator: &7" + baseGenerator + ">" + plotGenerator); + PlotSquared.log(C.PREFIX + "&3 - plotworld: &7" + plotArea.getClass().getName()); + PlotSquared + .log(C.PREFIX + "&3 - plotAreaManager: &7" + plotManager.getClass().getName()); + if (!this.worlds.contains(path)) { + this.worlds.createSection(path); + worldSection = this.worlds.getConfigurationSection(path); + } + plotArea.saveConfiguration(worldSection); + plotArea.loadDefaultConfiguration(worldSection); + try { + this.worlds.save(this.worldsFile); + } catch (IOException e) { + e.printStackTrace(); + } + // Now add it + addPlotArea(plotArea); + plotGenerator.initialize(plotArea); + } else { + if (!worlds.contains(world)) { + return; + } + ConfigurationSection areasSection = worldSection.getConfigurationSection("areas"); + if (areasSection == null) { + if (plotAreaManager.getPlotAreas(world, null).length != 0) { + debug("World possibly already loaded: " + world); + return; + } + PlotSquared.log(C.PREFIX + "&aDetected world load for '" + world + "'"); + String gen_string = worldSection.getString("generator.plugin", IMP.getPluginName()); + if (type == 2) { + Set clusters = + this.clusters_tmp != null ? this.clusters_tmp.get(world) : new HashSet<>(); + if (clusters == null) { + throw new IllegalArgumentException("No cluster exists for world: " + world); + } + ArrayDeque toLoad = new ArrayDeque<>(); + for (PlotCluster cluster : clusters) { + PlotId pos1 = cluster.getP1(); // Cluster pos1 + PlotId pos2 = cluster.getP2(); // Cluster pos2 + String name = cluster.getName(); // Cluster name + String fullId = name + "-" + pos1 + "-" + pos2; + worldSection.createSection("areas." + fullId); + DBFunc.replaceWorld(world, world + ";" + name, pos1, pos2); // NPE + + PlotSquared.log(C.PREFIX + "&3 - " + name + "-" + pos1 + "-" + pos2); + GeneratorWrapper areaGen = this.IMP.getGenerator(world, gen_string); + if (areaGen == null) { + throw new IllegalArgumentException("Invalid Generator: " + gen_string); + } + PlotArea pa = + areaGen.getPlotGenerator().getNewPlotArea(world, name, pos1, pos2); + pa.saveConfiguration(worldSection); + pa.loadDefaultConfiguration(worldSection); + try { + this.worlds.save(this.worldsFile); + } catch (IOException e) { + e.printStackTrace(); + } + PlotSquared + .log(C.PREFIX + "&c | &9generator: &7" + baseGenerator + ">" + areaGen); + PlotSquared.log(C.PREFIX + "&c | &9plotworld: &7" + pa); + PlotSquared.log(C.PREFIX + "&c | &9manager: &7" + pa); + PlotSquared.log(C.PREFIX + "&cNote: &7Area created for cluster:" + name + + " (invalid or old configuration?)"); + areaGen.getPlotGenerator().initialize(pa); + areaGen.augment(pa); + toLoad.add(pa); + } + for (PlotArea area : toLoad) { + addPlotArea(area); + } + return; + } + GeneratorWrapper areaGen = this.IMP.getGenerator(world, gen_string); + if (areaGen == null) { + throw new IllegalArgumentException("Invalid Generator: " + gen_string); + } + PlotArea pa = areaGen.getPlotGenerator().getNewPlotArea(world, null, null, null); + pa.saveConfiguration(worldSection); + pa.loadDefaultConfiguration(worldSection); + try { + this.worlds.save(this.worldsFile); + } catch (IOException e) { + e.printStackTrace(); + } + PlotSquared.log(C.PREFIX + "&3 - generator: &7" + baseGenerator + ">" + areaGen); + PlotSquared.log(C.PREFIX + "&3 - plotworld: &7" + pa); + PlotSquared.log(C.PREFIX + "&3 - plotAreaManager: &7" + pa.getPlotManager()); + areaGen.getPlotGenerator().initialize(pa); + areaGen.augment(pa); + addPlotArea(pa); + return; + } + if (type == 1) { + throw new IllegalArgumentException( + "Invalid type for multi-area world. Expected `2`, got `" + 1 + "`"); + } + for (String areaId : areasSection.getKeys(false)) { + PlotSquared.log(C.PREFIX + "&3 - " + areaId); + String[] split = areaId.split("(?<=[^;-])-"); + if (split.length != 3) { + throw new IllegalArgumentException("Invalid Area identifier: " + areaId + + ". Expected form `--`"); + } + String name = split[0]; + PlotId pos1 = PlotId.fromString(split[1]); + PlotId pos2 = PlotId.fromString(split[2]); + if (pos1 == null || pos2 == null || name.isEmpty()) { + throw new IllegalArgumentException("Invalid Area identifier: " + areaId + + ". Expected form `--`"); + } + PlotArea existing = getPlotArea(world, name); + if (existing != null && name.equals(existing.id)) { + continue; + } + ConfigurationSection section = areasSection.getConfigurationSection(areaId); + YamlConfiguration clone = new YamlConfiguration(); + for (String key : section.getKeys(true)) { + if (section.get(key) instanceof MemorySection) { + continue; + } + if (!clone.contains(key)) { + clone.set(key, section.get(key)); + } + } + for (String key : worldSection.getKeys(true)) { + if (worldSection.get(key) instanceof MemorySection) { + continue; + } + if (!key.startsWith("areas") && !clone.contains(key)) { + clone.set(key, worldSection.get(key)); + } + } + String gen_string = clone.getString("generator.plugin", IMP.getPluginName()); + GeneratorWrapper areaGen = this.IMP.getGenerator(world, gen_string); + if (areaGen == null) { + throw new IllegalArgumentException("Invalid Generator: " + gen_string); + } + PlotArea pa = areaGen.getPlotGenerator().getNewPlotArea(world, name, pos1, pos2); + pa.saveConfiguration(clone); + // netSections is the combination of + for (String key : clone.getKeys(true)) { + if (clone.get(key) instanceof MemorySection) { + continue; + } + if (!worldSection.contains(key)) { + worldSection.set(key, clone.get(key)); + } else { + Object value = worldSection.get(key); + if (!Objects.equals(value, clone.get(key))) { + section.set(key, clone.get(key)); + } + } + } + pa.loadDefaultConfiguration(clone); + try { + this.worlds.save(this.worldsFile); + } catch (IOException e) { + e.printStackTrace(); + } + PlotSquared.log(C.PREFIX + "&aDetected area load for '" + world + "'"); + PlotSquared.log(C.PREFIX + "&c | &9generator: &7" + baseGenerator + ">" + areaGen); + PlotSquared.log(C.PREFIX + "&c | &9plotworld: &7" + pa); + PlotSquared.log(C.PREFIX + "&c | &9manager: &7" + pa.getPlotManager()); + areaGen.getPlotGenerator().initialize(pa); + areaGen.augment(pa); + addPlotArea(pa); + } + } + } + + /** + * Setup the configuration for a plot world based on world arguments. + * + * + * e.g. /mv create <world> normal -g PlotSquared:<args> + * + * @param world The name of the world + * @param args The arguments + * @param generator the plot generator + * @return boolean | if valid arguments were provided + */ + public boolean setupPlotWorld(String world, String args, IndependentPlotGenerator generator) { + if (args != null && !args.isEmpty()) { + // save configuration + + final List validArguments = Arrays + .asList("s=", "size=", "g=", "gap=", "h=", "height=", "f=", "floor=", "m=", "main=", + "w=", "wall=", "b=", "border="); + + // Calculate the number of expected arguments + int expected = 0; + for (final String validArgument : validArguments) { + if (args.toLowerCase(Locale.ENGLISH).contains(validArgument)) { + expected += 1; + } + } + + String[] split = args.toLowerCase(Locale.ENGLISH).split(","); + + if (split.length > expected) { + // This means we have multi-block block buckets + String[] combinedArgs = new String[expected]; + int index = 0; + + StringBuilder argBuilder = new StringBuilder(); + outer: + for (final String string : split) { + for (final String validArgument : validArguments) { + if (string.contains(validArgument)) { + if (!argBuilder.toString().isEmpty()) { + combinedArgs[index++] = argBuilder.toString(); + argBuilder = new StringBuilder(); + } + argBuilder.append(string); + continue outer; + } + } + if (argBuilder.toString().charAt(argBuilder.length() - 1) != '=') { + argBuilder.append(","); + } + argBuilder.append(string); + } + + if (!argBuilder.toString().isEmpty()) { + combinedArgs[index] = argBuilder.toString(); + } + + split = combinedArgs; + } + + HybridPlotWorld plotworld = new HybridPlotWorld(world, null, generator, null, null); + for (String element : split) { + String[] pair = element.split("="); + if (pair.length != 2) { + PlotSquared.log("&cNo value provided for: &7" + element); + return false; + } + String key = pair[0].toLowerCase(); + String value = pair[1]; + String base = "worlds." + world + "."; + try { + switch (key) { + case "s": + case "size": + this.worlds.set(base + "plot.size", + Configuration.INTEGER.parseString(value).shortValue()); + break; + case "g": + case "gap": + this.worlds.set(base + "road.width", + Configuration.INTEGER.parseString(value).shortValue()); + break; + case "h": + case "height": + this.worlds.set(base + "road.height", + Configuration.INTEGER.parseString(value).shortValue()); + this.worlds.set(base + "plot.height", + Configuration.INTEGER.parseString(value).shortValue()); + this.worlds.set(base + "wall.height", + Configuration.INTEGER.parseString(value).shortValue()); + break; + case "f": + case "floor": + this.worlds.set(base + "plot.floor", + Configuration.BLOCK_BUCKET.parseString(value).toString()); + break; + case "m": + case "main": + this.worlds.set(base + "plot.filling", + Configuration.BLOCK_BUCKET.parseString(value).toString()); + break; + case "w": + case "wall": + this.worlds.set(base + "wall.filling", + Configuration.BLOCK_BUCKET.parseString(value).toString()); + break; + case "b": + case "border": + this.worlds.set(base + "wall.block", + Configuration.BLOCK_BUCKET.parseString(value).toString()); + break; + default: + PlotSquared.log("&cKey not found: &7" + element); + return false; + } + } catch (Exception e) { + e.printStackTrace(); + PlotSquared.log("&cInvalid value: &7" + value + " in arg " + element); + return false; + } + } + try { + ConfigurationSection section = + this.worlds.getConfigurationSection("worlds." + world); + plotworld.saveConfiguration(section); + plotworld.loadConfiguration(section); + this.worlds.save(this.worldsFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + return true; + } + + public boolean canUpdate(@NonNull final String current, @NonNull final String other) { + final String s1 = normalisedVersion(current); + final String s2 = normalisedVersion(other); + return s1.compareTo(s2) < 0; + } + + public String normalisedVersion(@NonNull final String version) { + final String[] split = Pattern.compile(".", Pattern.LITERAL).split(version); + final StringBuilder sb = new StringBuilder(); + for (final String s : split) { + sb.append(String.format("%" + 4 + 's', s)); + } + return sb.toString(); + } + + public boolean update(PlotPlayer sender, URL url) { + try { + String name = this.jarFile.getName(); + File newJar = new File("plugins/update/" + name); + MainUtil.sendMessage(sender, "$1Downloading from provided URL: &7" + url); + URLConnection con = url.openConnection(); + try (InputStream stream = con.getInputStream()) { + File parent = newJar.getParentFile(); + if (!parent.exists()) { + parent.mkdirs(); + } + MainUtil.sendMessage(sender, "$2 - Output: " + newJar); + if (!newJar.delete()) { + MainUtil.sendMessage(sender, "Failed to update " + IMP.getPluginName() + ""); + MainUtil.sendMessage(sender, "Jar file failed to delete."); + MainUtil.sendMessage(sender, " - Please update manually"); + } + Files.copy(stream, newJar.toPath()); + } + MainUtil.sendMessage(sender, + "$1The update will take effect when the server is restarted next"); + return true; + } catch (IOException e) { + MainUtil.sendMessage(sender, "Failed to update " + IMP.getPluginName() + ""); + MainUtil.sendMessage(sender, " - Please update manually"); + PlotSquared.log("============ Stacktrace ============"); + e.printStackTrace(); + PlotSquared.log("===================================="); + } + return false; + } + + /** + * Copy a file from inside the jar to a location + * + * @param file Name of the file inside PlotSquared.jar + * @param folder The output location relative to /plugins/PlotSquared/ + */ + public void copyFile(String file, String folder) { + try { + File output = this.IMP.getDirectory(); + if (!output.exists()) { + output.mkdirs(); + } + File newFile = MainUtil.getFile(output, folder + File.separator + file); + if (newFile.exists()) { + return; + } + try (InputStream stream = this.IMP.getClass().getResourceAsStream(file)) { + byte[] buffer = new byte[2048]; + if (stream == null) { + try (ZipInputStream zis = new ZipInputStream( + new FileInputStream(this.jarFile))) { + ZipEntry ze = zis.getNextEntry(); + while (ze != null) { + String name = ze.getName(); + if (name.equals(file)) { + new File(newFile.getParent()).mkdirs(); + try (FileOutputStream fos = new FileOutputStream(newFile)) { + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + } + ze = null; + } else { + ze = zis.getNextEntry(); + } + } + zis.closeEntry(); + } + return; + } + newFile.createNewFile(); + try (FileOutputStream fos = new FileOutputStream(newFile)) { + int len; + while ((len = stream.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + PlotSquared.log("&cCould not save " + file); + } + } + + private Map> getPlotsRaw() { + HashMap> map = new HashMap<>(); + for (PlotArea area : this.plotAreaManager.getAllPlotAreas()) { + Map map2 = map.get(area.toString()); + if (map2 == null) { + map.put(area.toString(), area.getPlotsRaw()); + } else { + map2.putAll(area.getPlotsRaw()); + } + } + return map; + } + + /** + * Close the database connection. + */ + public void disable() { + try { + // Validate that all data in the db is correct + final HashSet plots = new HashSet<>(); + try { + foreachPlotRaw(new RunnableVal() { + @Override public void run(Plot value) { + plots.add(value); + } + }); + } catch (final Exception ignored) { + } + DBFunc.validatePlots(plots); + + // Close the connection + DBFunc.close(); + UUIDHandler.handleShutdown(); + } catch (NullPointerException exception) { + exception.printStackTrace(); + PlotSquared.log("&cCould not close database connection!"); + } + } + + /** + * Setup the database connection. + */ + public void setupDatabase() { + try { + if (DBFunc.dbManager != null) { + DBFunc.dbManager.close(); + } + Database database; + if (Storage.MySQL.USE) { + database = new MySQL(Storage.MySQL.HOST, Storage.MySQL.PORT, Storage.MySQL.DATABASE, + Storage.MySQL.USER, Storage.MySQL.PASSWORD); + } else if (Storage.SQLite.USE) { + File file = MainUtil.getFile(IMP.getDirectory(), Storage.SQLite.DB + ".db"); + database = new SQLite(file); + } else { + PlotSquared.log(C.PREFIX + "&cNo storage type is set!"); + this.IMP.disable(); + return; + } + DBFunc.dbManager = new SQLManager(database, Storage.PREFIX, false); + PlotSquared.log("GETTING PLOTS NOW AND STORING TO PLOTS_TMP"); + for (PlotArea allPlotArea : plotAreaManager.getAllPlotAreas()) { + PlotSquared.log(allPlotArea.toString()); + } + + this.plots_tmp = DBFunc.getPlots(); + if (plotAreaManager instanceof SinglePlotAreaManager) { + SinglePlotArea area = ((SinglePlotAreaManager) plotAreaManager).getArea(); + addPlotArea(area); + ConfigurationSection section = worlds.getConfigurationSection("worlds.*"); + if (section == null) { + section = worlds.createSection("worlds.*"); + } + area.saveConfiguration(section); + area.loadDefaultConfiguration(section); + } + this.clusters_tmp = DBFunc.getClusters(); + } catch (ClassNotFoundException | SQLException e) { + PlotSquared.log( + C.PREFIX + "&cFailed to open DATABASE connection. The plugin will disable itself."); + if (Storage.MySQL.USE) { + PlotSquared.log("$4MYSQL"); + } else if (Storage.SQLite.USE) { + PlotSquared.log("$4SQLITE"); + } + PlotSquared.log( + "&d==== Here is an ugly stacktrace, if you are interested in those things ==="); + e.printStackTrace(); + PlotSquared.log("&d==== End of stacktrace ===="); + PlotSquared.log("&6Please go to the " + IMP.getPluginName() + + " 'storage.yml' and configure the database correctly."); + this.IMP.disable(); + } + } + + /** + * Setup the default configuration. + * + * @throws IOException if the config failed to save + */ + public void setupConfig() throws IOException { + String lastVersionString = this.config.getString("version"); + if (lastVersionString != null) { + String[] split = lastVersionString.split("\\."); + int[] lastVersion = new int[] {Integer.parseInt(split[0]), Integer.parseInt(split[1]), + Integer.parseInt(split[2])}; + if (checkVersion(new int[] {3, 4, 0}, lastVersion)) { + Settings.convertLegacy(configFile); + if (config.contains("worlds")) { + ConfigurationSection worldSection = config.getConfigurationSection("worlds"); + worlds.set("worlds", worldSection); + try { + worlds.save(worldsFile); + } catch (IOException e) { + PlotSquared.debug("Failed to save " + IMP.getPluginName() + " worlds.yml"); + e.printStackTrace(); + } + } + Settings.save(configFile); + } + } + Settings.load(configFile); + try { + InputStream stream = getClass().getResourceAsStream("/plugin.properties"); + BufferedReader br = new BufferedReader(new InputStreamReader(stream)); + //java.util.Scanner scanner = new java.util.Scanner(stream).useDelimiter("\\A"); + String versionString = br.readLine(); + String commitString = br.readLine(); + String dateString = br.readLine(); + //scanner.close(); + br.close(); + this.version = PlotVersion.tryParse(versionString, commitString, dateString); + Settings.DATE = new Date(100 + version.year, version.month, version.day).toGMTString(); + Settings.BUILD = "https://ci.athion.net/job/PlotSquared/" + version.build; + Settings.COMMIT = "https://github.com/IntellectualSites/PlotSquared/commit/" + Integer + .toHexString(version.hash); + System.out.println("Version is " + this.version); + } catch (IOException exception) { + exception.printStackTrace(); + } + Settings.save(configFile); + config = YamlConfiguration.loadConfiguration(configFile); + } + + /** + * Setup all configuration files
- Config: settings.yml
- Storage: storage.yml
- + * Translation: PlotSquared.use_THIS.yml, style.yml
+ */ + public boolean setupConfigs() { + File folder = new File(this.IMP.getDirectory(), "config"); + if (!folder.exists() && !folder.mkdirs()) { + PlotSquared.log(C.PREFIX + + "&cFailed to create the /plugins/config folder. Please create it manually."); + } + try { + this.worldsFile = new File(folder, "worlds.yml"); + if (!this.worldsFile.exists() && !this.worldsFile.createNewFile()) { + PlotSquared.log( + "Could not create the worlds file, please create \"worlds.yml\" manually."); + } + this.worlds = YamlConfiguration.loadConfiguration(this.worldsFile); + + if (this.worlds.contains("worlds")) { + if (!this.worlds.contains("configuration_version") || !this.worlds + .getString("configuration_version") + .equalsIgnoreCase(LegacyConverter.CONFIGURATION_VERSION)) { + // Conversion needed + log(C.LEGACY_CONFIG_FOUND.s()); + try { + com.google.common.io.Files + .copy(this.worldsFile, new File(folder, "worlds.yml.old")); + log(C.LEGACY_CONFIG_BACKUP.s()); + final ConfigurationSection worlds = + this.worlds.getConfigurationSection("worlds"); + final LegacyConverter converter = new LegacyConverter(worlds); + converter.convert(); + this.worlds + .set("configuration_version", LegacyConverter.CONFIGURATION_VERSION); + this.worlds.set("worlds", worlds); // Redundant, but hey... ¯\_(ツ)_/¯ + this.worlds.save(this.worldsFile); + log(C.LEGACY_CONFIG_DONE.s()); + } catch (final Exception e) { + log(C.LEGACY_CONFIG_CONVERSION_FAILED.s()); + e.printStackTrace(); + } + // Disable plugin + this.IMP.shutdown(); + return false; + } + } else { + this.worlds.set("configuration_version", LegacyConverter.CONFIGURATION_VERSION); + } + } catch (IOException ignored) { + PlotSquared.log("Failed to save settings.yml"); + } + try { + this.configFile = new File(folder, "settings.yml"); + if (!this.configFile.exists() && !this.configFile.createNewFile()) { + PlotSquared.log( + "Could not create the settings file, please create \"settings.yml\" manually."); + } + this.config = YamlConfiguration.loadConfiguration(this.configFile); + setupConfig(); + } catch (IOException ignored) { + PlotSquared.log("Failed to save settings.yml"); + } + try { + this.styleFile = MainUtil.getFile(IMP.getDirectory(), + Settings.Paths.TRANSLATIONS + File.separator + "style.yml"); + if (!this.styleFile.exists()) { + if (!this.styleFile.getParentFile().exists()) { + this.styleFile.getParentFile().mkdirs(); + } + if (!this.styleFile.createNewFile()) { + PlotSquared.log( + "Could not create the style file, please create \"translations/style.yml\" manually"); + } + } + this.style = YamlConfiguration.loadConfiguration(this.styleFile); + setupStyle(); + } catch (IOException err) { + err.printStackTrace(); + PlotSquared.log("failed to save style.yml"); + } + try { + this.storageFile = new File(folder, "storage.yml"); + if (!this.storageFile.exists() && !this.storageFile.createNewFile()) { + PlotSquared.log( + "Could not the storage settings file, please create \"storage.yml\" manually."); + } + this.storage = YamlConfiguration.loadConfiguration(this.storageFile); + setupStorage(); + } catch (IOException ignored) { + PlotSquared.log("Failed to save storage.yml"); + } + try { + this.commandsFile = new File(folder, "commands.yml"); + if (!this.commandsFile.exists() && !this.commandsFile.createNewFile()) { + PlotSquared.log( + "Could not the storage settings file, please create \"commands.yml\" manually."); + } + this.commands = YamlConfiguration.loadConfiguration(this.commandsFile); + } catch (IOException ignored) { + PlotSquared.log("Failed to save commands.yml"); + } + try { + this.style.save(this.styleFile); + this.commands.save(this.commandsFile); + } catch (IOException e) { + PlotSquared.log("Configuration file saving failed"); + e.printStackTrace(); + } + return true; + } + + /** + * Setup the storage file (load + save missing nodes). + */ + private void setupStorage() { + Storage.load(storageFile); + Storage.save(storageFile); + storage = YamlConfiguration.loadConfiguration(storageFile); + } + + /** + * Show startup debug information. + */ + private void showDebug() { + if (Settings.DEBUG) { + Map components = Settings.getFields(Settings.Enabled_Components.class); + for (Entry component : components.entrySet()) { + PlotSquared.log(C.PREFIX + String + .format("&cKey: &6%s&c, Value: &6%s", component.getKey(), + component.getValue())); + } + } + } + + /** + * Setup the style.yml file + */ + private void setupStyle() { + if (this.version != null) { + this.style.set("version", this.version.toString()); + } + Map o = new HashMap<>(4); + o.put("color.1", "6"); + o.put("color.2", "7"); + o.put("color.3", "8"); + o.put("color.4", "3"); + if (!this.style.contains("color")) { + for (Entry node : o.entrySet()) { + this.style.set(node.getKey(), node.getValue()); + } + } + } + + /** + * Get the Java version. + * + * @return the java version + */ + public double getJavaVersion() { + return Double.parseDouble(System.getProperty("java.specification.version")); + } + + public void foreachPlotArea(@NonNull final RunnableVal runnable) { + for (final PlotArea area : this.plotAreaManager.getAllPlotAreas()) { + runnable.run(area); + } + } + + public void foreachPlotArea(@NonNull final String world, + @NonNull final RunnableVal runnable) { + final PlotArea[] array = this.plotAreaManager.getPlotAreas(world, null); + if (array == null) { + return; + } + for (final PlotArea area : array) { + runnable.run(area); + } + } + + public void foreachPlot(@NonNull final RunnableVal runnable) { + for (final PlotArea area : this.plotAreaManager.getAllPlotAreas()) { + area.getPlots().forEach(runnable::run); + } + } + + public void foreachPlotRaw(@NonNull final RunnableVal runnable) { + for (final PlotArea area : this.plotAreaManager.getAllPlotAreas()) { + area.getPlots().forEach(runnable::run); + } + if (this.plots_tmp != null) { + for (final HashMap entry : this.plots_tmp.values()) { + entry.values().forEach(runnable::run); + } + } + } + + public void foreachBasePlot(@NonNull final RunnableVal run) { + for (final PlotArea area : this.plotAreaManager.getAllPlotAreas()) { + area.foreachBasePlot(run); + } + } + + public PlotArea getFirstPlotArea() { + PlotArea[] areas = plotAreaManager.getAllPlotAreas(); + return areas.length > 0 ? areas[0] : null; + } + + public int getPlotAreaCount() { + return this.plotAreaManager.getAllPlotAreas().length; + } + + public int getPlotCount() { + int count = 0; + for (final PlotArea area : this.plotAreaManager.getAllPlotAreas()) { + count += area.getPlotCount(); + } + return count; + } + + public Set getPlotAreas() { + final Set set = new HashSet<>(); + Collections.addAll(set, plotAreaManager.getAllPlotAreas()); + return Collections.unmodifiableSet(set); + } + + public boolean isAugmented(@NonNull final String world) { + final PlotArea[] areas = plotAreaManager.getPlotAreas(world, null); + return areas != null && (areas.length > 1 || areas[0].TYPE != 0); + } + + /** + * Get a list of PlotArea objects. + * + * @param world the world + * @return Collection of PlotArea objects + */ + public Set getPlotAreas(@NonNull final String world) { + final Set set = new HashSet<>(); + Collections.addAll(set, plotAreaManager.getPlotAreas(world, null)); + return set; + } + + /** + * Get the relevant plot area for a specified location. + *
    + *
  • If there is only one plot area globally that will be returned. + *
  • If there is only one plot area in the world, it will return that. + *
  • If the plot area for a location cannot be unambiguously + * resolved, null will be returned. + *
+ * Note: An applicable plot area may not include the location i.e. clusters + * + * @param location the location + */ + public PlotArea getApplicablePlotArea(@NonNull final Location location) { + return plotAreaManager.getApplicablePlotArea(location); + } + + public PlotArea getPlotArea(@NonNull final String world, final String id) { + return plotAreaManager.getPlotArea(world, id); + } + + /** + * Get the {@code PlotArea} which contains a location. + *
    + *
  • If the plot area does not contain a location, null + * will be returned. + *
+ * + * @param location the location + * @return the {@link PlotArea} in the location, null if non existent + */ + public PlotArea getPlotAreaAbs(@NonNull final Location location) { + return plotAreaManager.getPlotArea(location); + } + + public PlotArea getPlotAreaByString(@NonNull final String search) { + String[] split = search.split("[;,]"); + PlotArea[] areas = plotAreaManager.getPlotAreas(split[0], null); + if (areas == null) { + for (PlotArea area : plotAreaManager.getAllPlotAreas()) { + if (area.worldname.equalsIgnoreCase(split[0])) { + if (area.id == null || split.length == 2 && area.id + .equalsIgnoreCase(split[1])) { + return area; + } + } + } + return null; + } + if (areas.length == 1) { + return areas[0]; + } else if (split.length == 1) { + return null; + } else { + for (PlotArea area : areas) { + if (StringMan.isEqual(split[1], area.id)) { + return area; + } + } + return null; + } + } + + /** + * Get Plots based on alias + * + * @param alias to search plots + * @param worldname to filter alias to a specific world [optional] null means all worlds + * @return Set<{ + * @ link + * < p> + *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ * Plot + *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ *

+ * }> empty if nothing found + */ + public Set getPlotsByAlias(@Nullable final String alias, + @NonNull final String worldname) { + final Set result = new HashSet<>(); + if (alias != null) { + for (final Plot plot : getPlots()) { + if (alias.equals(plot.getAlias()) && (worldname == null || worldname + .equals(plot.getWorldName()))) { + result.add(plot); + } + } + } + return Collections.unmodifiableSet(result); + } + + public Set getPlotAreas(final String world, final RegionWrapper region) { + final PlotArea[] areas = plotAreaManager.getPlotAreas(world, region); + final Set set = new HashSet<>(); + Collections.addAll(set, areas); + return Collections.unmodifiableSet(set); + } + + public enum SortType { + CREATION_DATE, CREATION_DATE_TIMESTAMP, LAST_MODIFIED, DISTANCE_FROM_ORIGIN + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/PlotVersion.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/PlotVersion.java new file mode 100644 index 000000000..ecb423970 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/PlotVersion.java @@ -0,0 +1,45 @@ +package com.github.intellectualsites.plotsquared.plot; + +public class PlotVersion { + public final int year, month, day, hash, build; + + public PlotVersion(int year, int month, int day, int hash, int build) { + this.year = year; + this.month = month; + this.day = day; + this.hash = hash; + this.build = build; + } + + public PlotVersion(String version, String commit, String date) { + String[] split = version.substring(version.indexOf('=') + 1).split("\\."); + this.build = Integer.parseInt(split[1]); + this.hash = Integer.parseInt(commit.substring(commit.indexOf('=') + 1), 16); + String[] split1 = date.substring(date.indexOf('=') + 1).split("\\."); + this.year = Integer.parseInt(split1[0]); + this.month = Integer.parseInt(split1[1]); + this.day = Integer.parseInt(split1[2]); + } + + public static PlotVersion tryParse(String version, String commit, String date) { + try { + return new PlotVersion(version, commit, date); + } catch (Exception ignore) { + ignore.printStackTrace(); + return new PlotVersion(0, 0, 0, 0, 0); + } + } + + @Override public String toString() { + if (hash == 0 && build == 0) { + return "PlotSquared-" + year + "." + month + "." + day + "-SNAPSHOT"; + } else { + return "PlotSquared-" + year + "." + month + "." + day + "-" + Integer.toHexString(hash) + + "-" + build; + } + } + + public boolean isNewer(PlotVersion other) { + return other.build < this.build; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Add.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Add.java new file mode 100644 index 000000000..03d27555a --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Add.java @@ -0,0 +1,82 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal2; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal3; +import com.github.intellectualsites.plotsquared.plot.util.EventUtil; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; + +import java.util.Iterator; +import java.util.Set; +import java.util.UUID; + +@CommandDeclaration(command = "add", + description = "Allow a user to build in a plot while you are online", + usage = "/plot add ", category = CommandCategory.SETTINGS, permission = "plots.add", + requiredType = RequiredType.NONE) public class Add extends Command { + + public Add() { + super(MainCommand.getInstance(), true); + } + + @Override public void execute(final PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) throws CommandException { + final Plot plot = check(player.getCurrentPlot(), C.NOT_IN_PLOT); + checkTrue(plot.hasOwner(), C.PLOT_UNOWNED); + checkTrue(plot.isOwner(player.getUUID()) || Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_TRUST), C.NO_PLOT_PERMS); + checkTrue(args.length == 1, C.COMMAND_SYNTAX, getUsage()); + final Set uuids = MainUtil.getUUIDsFromString(args[0]); + checkTrue(!uuids.isEmpty(), C.INVALID_PLAYER, args[0]); + Iterator iter = uuids.iterator(); + int size = plot.getTrusted().size() + plot.getMembers().size(); + while (iter.hasNext()) { + UUID uuid = iter.next(); + if (uuid == DBFunc.EVERYONE && !( + Permissions.hasPermission(player, C.PERMISSION_TRUST_EVERYONE) || Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_TRUST))) { + MainUtil.sendMessage(player, C.INVALID_PLAYER, MainUtil.getName(uuid)); + iter.remove(); + continue; + } + if (plot.isOwner(uuid)) { + MainUtil.sendMessage(player, C.ALREADY_OWNER, MainUtil.getName(uuid)); + iter.remove(); + continue; + } + if (plot.getMembers().contains(uuid)) { + MainUtil.sendMessage(player, C.ALREADY_ADDED, MainUtil.getName(uuid)); + iter.remove(); + continue; + } + size += plot.getTrusted().contains(uuid) ? 0 : 1; + } + checkTrue(!uuids.isEmpty(), null); + checkTrue(size <= plot.getArea().MAX_PLOT_MEMBERS || Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_TRUST), C.PLOT_MAX_MEMBERS); + confirm.run(this, new Runnable() { + @Override // Success + public void run() { + for (UUID uuid : uuids) { + if (uuid != DBFunc.EVERYONE) { + if (!plot.removeTrusted(uuid)) { + if (plot.getDenied().contains(uuid)) { + plot.removeDenied(uuid); + } + } + } + plot.addMember(uuid); + EventUtil.manager.callMember(player, plot, uuid, true); + MainUtil.sendMessage(player, C.MEMBER_ADDED); + } + } + }, null); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Alias.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Alias.java new file mode 100644 index 000000000..2eeb0a49d --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Alias.java @@ -0,0 +1,123 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.StringWrapper; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.MathMan; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; + +@CommandDeclaration(command = "setalias", permission = "plots.alias", + description = "Set the plot name", usage = "/plot alias ", + aliases = {"alias", "sa", "name", "rename", "setname", "seta", "nameplot"}, + category = CommandCategory.SETTINGS, requiredType = RequiredType.NONE) public class Alias + extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + + if (args.length == 0) { + C.COMMAND_SYNTAX.send(player, "/plot alias "); + return false; + } + + Location loc = player.getLocation(); + Plot plot = loc.getPlotAbs(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + + if (!plot.hasOwner()) { + sendMessage(player, C.PLOT_NOT_CLAIMED); + return false; + } + + if (!plot.isOwner(player.getUUID())) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + + boolean result = false; + + switch (args[0].toLowerCase()) { + case "set": + if (args.length != 2) { + C.COMMAND_SYNTAX.send(player, "/plot alias "); + return false; + } + + if (canExecuteCommand(player, C.PERMISSION_ALIAS_SET, false) || canExecuteCommand( + player, C.PERMISSION_ALIAS_SET_OBSOLETE, false)) { + result = setAlias(player, plot, args[1]); + } else { + MainUtil.sendMessage(player, C.NO_PERMISSION); + } + + break; + case "remove": + if (canExecuteCommand(player, C.PERMISSION_ALIAS_REMOVE, true)) { + result = removeAlias(player, plot); + } + break; + default: + C.COMMAND_SYNTAX.send(player, "/plot alias "); + result = false; + } + + return result; + } + + + private boolean setAlias(PlotPlayer player, Plot plot, String alias) { + if (alias.isEmpty()) { + C.COMMAND_SYNTAX.send(player, "/plot alias "); + return false; + } + if (alias.length() >= 50) { + MainUtil.sendMessage(player, C.ALIAS_TOO_LONG); + return false; + } + if (alias.contains(" ")) { + C.NOT_VALID_VALUE.send(player); + return false; + } + if (MathMan.isInteger(alias)) { + C.NOT_VALID_VALUE.send(player); + return false; + } + for (Plot p : PlotSquared.get().getPlots(plot.getArea())) { + if (p.getAlias().equalsIgnoreCase(alias)) { + MainUtil.sendMessage(player, C.ALIAS_IS_TAKEN); + return false; + } + } + if (UUIDHandler.nameExists(new StringWrapper(alias)) || PlotSquared.get() + .hasPlotArea(alias)) { + MainUtil.sendMessage(player, C.ALIAS_IS_TAKEN); + return false; + } + plot.setAlias(alias); + MainUtil.sendMessage(player, C.ALIAS_SET_TO.s().replaceAll("%alias%", alias)); + return true; + } + + private boolean removeAlias(PlotPlayer player, Plot plot) { + plot.setAlias(null); + MainUtil.sendMessage(player, C.ALIAS_REMOVED.s()); + return true; + } + + private boolean canExecuteCommand(PlotPlayer player, C caption, boolean sendMessage) { + if (!Permissions.hasPermission(player, caption)) { + if (sendMessage) { + MainUtil.sendMessage(player, C.NO_PERMISSION); + } + return false; + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Area.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Area.java new file mode 100644 index 000000000..ee7c1cb23 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Area.java @@ -0,0 +1,469 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.configuration.ConfigurationSection; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Configuration; +import com.github.intellectualsites.plotsquared.plot.generator.AugmentedUtils; +import com.github.intellectualsites.plotsquared.plot.generator.HybridPlotWorld; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Objects; +import java.util.Set; + +@CommandDeclaration(command = "area", permission = "plots.area", + category = CommandCategory.ADMINISTRATION, requiredType = RequiredType.NONE, + description = "Create a new PlotArea", aliases = "world", + usage = "/plot area ", confirmation = true) public class Area + extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + if (args.length == 0) { + C.COMMAND_SYNTAX.send(player, getUsage()); + return false; + } + switch (args[0].toLowerCase()) { + case "c": + case "setup": + case "create": + if (!Permissions.hasPermission(player, C.PERMISSION_AREA_CREATE)) { + C.NO_PERMISSION.send(player, C.PERMISSION_AREA_CREATE); + return false; + } + switch (args.length) { + case 1: + C.COMMAND_SYNTAX + .send(player, "/plot area create [world[:id]] [=]..."); + return false; + case 2: + switch (args[1].toLowerCase()) { + case "pos1": { // Set position 1 + HybridPlotWorld area = player.getMeta("area_create_area"); + if (area == null) { + C.COMMAND_SYNTAX.send(player, + "/plot area create [world[:id]] [=]..."); + return false; + } + Location location = player.getLocation(); + player.setMeta("area_pos1", location); + C.SET_ATTRIBUTE.send(player, "area_pos1", + location.getX() + "," + location.getZ()); + MainUtil.sendMessage(player, + "You will now set pos2: /plot area create pos2" + + "\nNote: The chosen plot size may result in the created area not exactly matching your second position."); + return true; + } + case "pos2": // Set position 2 and finish creation for type=2 (partial) + final HybridPlotWorld area = player.getMeta("area_create_area"); + if (area == null) { + C.COMMAND_SYNTAX.send(player, + "/plot area create [world[:id]] [=]..."); + return false; + } + Location pos1 = player.getLocation(); + Location pos2 = player.getMeta("area_pos1"); + int dx = Math.abs(pos1.getX() - pos2.getX()); + int dz = Math.abs(pos1.getZ() - pos2.getZ()); + int numX = Math.max(1, + (dx + 1 + area.ROAD_WIDTH + area.SIZE / 2) / area.SIZE); + int numZ = Math.max(1, + (dz + 1 + area.ROAD_WIDTH + area.SIZE / 2) / area.SIZE); + int ddx = dx - (numX * area.SIZE - area.ROAD_WIDTH); + int ddz = dz - (numZ * area.SIZE - area.ROAD_WIDTH); + int bx = Math.min(pos1.getX(), pos2.getX()) + ddx; + int bz = Math.min(pos1.getZ(), pos2.getZ()) + ddz; + int tx = Math.max(pos1.getX(), pos2.getX()) - ddx; + int tz = Math.max(pos1.getZ(), pos2.getZ()) - ddz; + int lower = (area.ROAD_WIDTH & 1) == 0 ? + area.ROAD_WIDTH / 2 - 1 : + area.ROAD_WIDTH / 2; + final int offsetX = bx - (area.ROAD_WIDTH == 0 ? 0 : lower); + final int offsetZ = bz - (area.ROAD_WIDTH == 0 ? 0 : lower); + final RegionWrapper region = new RegionWrapper(bx, tx, bz, tz); + Set areas = + PlotSquared.get().getPlotAreas(area.worldname, region); + if (!areas.isEmpty()) { + C.CLUSTER_INTERSECTION + .send(player, areas.iterator().next().toString()); + return false; + } + final SetupObject object = new SetupObject(); + object.world = area.worldname; + object.id = area.id; + object.terrain = area.TERRAIN; + object.type = area.TYPE; + object.min = new PlotId(1, 1); + object.max = new PlotId(numX, numZ); + object.plotManager = PlotSquared.imp().getPluginName(); + object.setupGenerator = PlotSquared.imp().getPluginName(); + object.step = area.getSettingNodes(); + final String path = + "worlds." + area.worldname + ".areas." + area.id + '-' + + object.min + '-' + object.max; + Runnable run = () -> { + if (offsetX != 0) { + PlotSquared.get().worlds + .set(path + ".road.offset.x", offsetX); + } + if (offsetZ != 0) { + PlotSquared.get().worlds + .set(path + ".road.offset.z", offsetZ); + } + final String world = SetupUtils.manager.setupWorld(object); + if (WorldUtil.IMP.isWorld(world)) { + PlotSquared.get().loadWorld(world, null); + C.SETUP_FINISHED.send(player); + player.teleport(WorldUtil.IMP.getSpawn(world)); + if (area.TERRAIN != 3) { + ChunkManager.largeRegionTask(world, region, + new RunnableVal() { + @Override public void run(ChunkLoc value) { + AugmentedUtils + .generate(world, value.x, value.z, + null); + } + }, null); + } + } else { + MainUtil.sendMessage(player, + "An error occurred while creating the world: " + + area.worldname); + } + }; + if (hasConfirmation(player)) { + CmdConfirm.addPending(player, + getCommandString() + " create pos2 (Creates world)", run); + } else { + run.run(); + } + return true; + } + default: // Start creation + final SetupObject object = new SetupObject(); + String[] split = args[1].split(":"); + String id; + if (split.length == 2) { + id = split[1]; + } else { + id = null; + } + object.world = split[0]; + final HybridPlotWorld pa = new HybridPlotWorld(object.world, id, + PlotSquared.get().IMP.getDefaultGenerator(), null, null); + PlotArea other = PlotSquared.get().getPlotArea(pa.worldname, id); + if (other != null && Objects.equals(pa.id, other.id)) { + C.SETUP_WORLD_TAKEN.send(player, pa.toString()); + return false; + } + Set areas = PlotSquared.get().getPlotAreas(pa.worldname); + if (!areas.isEmpty()) { + PlotArea area = areas.iterator().next(); + pa.TYPE = area.TYPE; + } + pa.SIZE = (short) (pa.PLOT_WIDTH + pa.ROAD_WIDTH); + for (int i = 2; i < args.length; i++) { + String[] pair = args[i].split("="); + if (pair.length != 2) { + C.COMMAND_SYNTAX.send(player, getCommandString() + + " create [world[:id]] [=]..."); + return false; + } + switch (pair[0].toLowerCase()) { + case "s": + case "size": + pa.PLOT_WIDTH = Integer.parseInt(pair[1]); + pa.SIZE = (short) (pa.PLOT_WIDTH + pa.ROAD_WIDTH); + break; + case "g": + case "gap": + pa.ROAD_WIDTH = Integer.parseInt(pair[1]); + pa.SIZE = (short) (pa.PLOT_WIDTH + pa.ROAD_WIDTH); + break; + case "h": + case "height": + int value = Integer.parseInt(pair[1]); + pa.PLOT_HEIGHT = value; + pa.ROAD_HEIGHT = value; + pa.WALL_HEIGHT = value; + break; + case "f": + case "floor": + pa.TOP_BLOCK = Configuration.BLOCK_BUCKET.parseString(pair[1]); + break; + case "m": + case "main": + pa.MAIN_BLOCK = Configuration.BLOCK_BUCKET.parseString(pair[1]); + break; + case "w": + case "wall": + pa.WALL_FILLING = + Configuration.BLOCK_BUCKET.parseString(pair[1]); + break; + case "b": + case "border": + pa.WALL_BLOCK = Configuration.BLOCK_BUCKET.parseString(pair[1]); + break; + case "terrain": + pa.TERRAIN = Integer.parseInt(pair[1]); + object.terrain = pa.TERRAIN; + break; + case "type": + pa.TYPE = Integer.parseInt(pair[1]); + object.type = pa.TYPE; + break; + default: + C.COMMAND_SYNTAX.send(player, getCommandString() + + " create [world[:id]] [=]..."); + return false; + } + } + if (pa.TYPE != 2) { + if (WorldUtil.IMP.isWorld(pa.worldname)) { + C.SETUP_WORLD_TAKEN.send(player, pa.worldname); + return false; + } + Runnable run = () -> { + String path = "worlds." + pa.worldname; + if (!PlotSquared.get().worlds.contains(path)) { + PlotSquared.get().worlds.createSection(path); + } + ConfigurationSection section = + PlotSquared.get().worlds.getConfigurationSection(path); + pa.saveConfiguration(section); + pa.loadConfiguration(section); + object.plotManager = PlotSquared.imp().getPluginName(); + object.setupGenerator = PlotSquared.imp().getPluginName(); + String world = SetupUtils.manager.setupWorld(object); + if (WorldUtil.IMP.isWorld(world)) { + C.SETUP_FINISHED.send(player); + player.teleport(WorldUtil.IMP.getSpawn(world)); + } else { + MainUtil.sendMessage(player, + "An error occurred while creating the world: " + + pa.worldname); + } + try { + PlotSquared.get().worlds.save(PlotSquared.get().worldsFile); + } catch (IOException e) { + e.printStackTrace(); + } + }; + if (hasConfirmation(player)) { + CmdConfirm.addPending(player, + getCommandString() + ' ' + StringMan.join(args, " "), run); + } else { + run.run(); + } + return true; + } + if (pa.id == null) { + C.COMMAND_SYNTAX.send(player, getCommandString() + + " create [world[:id]] [=]..."); + return false; + } + if (WorldUtil.IMP.isWorld(pa.worldname)) { + if (!player.getLocation().getWorld().equals(pa.worldname)) { + player.teleport(WorldUtil.IMP.getSpawn(pa.worldname)); + } + } else { + object.terrain = 0; + object.type = 0; + SetupUtils.manager.setupWorld(object); + player.teleport(WorldUtil.IMP.getSpawn(pa.worldname)); + } + player.setMeta("area_create_area", pa); + MainUtil.sendMessage(player, + "$1Go to the first corner and use: $2 " + getCommandString() + + " create pos1"); + break; + } + return true; + case "i": + case "info": { + if (!Permissions.hasPermission(player, C.PERMISSION_AREA_INFO)) { + C.NO_PERMISSION.send(player, C.PERMISSION_AREA_INFO); + return false; + } + PlotArea area; + switch (args.length) { + case 1: + area = player.getApplicablePlotArea(); + break; + case 2: + area = PlotSquared.get().getPlotAreaByString(args[1]); + break; + default: + C.COMMAND_SYNTAX.send(player, getCommandString() + " info [area]"); + return false; + } + if (area == null) { + if (args.length == 2) { + C.NOT_VALID_PLOT_WORLD.send(player, args[1]); + } else { + C.NOT_IN_PLOT_WORLD.send(player); + } + return false; + } + String name; + double percent; + int claimed = area.getPlotCount(); + int clusters = area.getClusters().size(); + String region; + String generator = String.valueOf(area.getGenerator()); + if (area.TYPE == 2) { + PlotId min = area.getMin(); + PlotId max = area.getMax(); + name = area.worldname + ';' + area.id + ';' + min + ';' + max; + int size = (max.x - min.x + 1) * (max.y - min.y + 1); + percent = claimed == 0 ? 0 : size / (double) claimed; + region = area.getRegion().toString(); + } else { + name = area.worldname; + percent = claimed == 0 ? 0 : 100d * claimed / Integer.MAX_VALUE; + region = "N/A"; + } + String value = "&r$1NAME: " + name + "\n$1Type: $2" + area.TYPE + "\n$1Terrain: $2" + + area.TERRAIN + "\n$1Usage: $2" + String.format("%.2f", percent) + '%' + + "\n$1Claimed: $2" + claimed + "\n$1Clusters: $2" + clusters + "\n$1Region: $2" + + region + "\n$1Generator: $2" + generator; + MainUtil.sendMessage(player, + C.PLOT_INFO_HEADER.s() + '\n' + value + '\n' + C.PLOT_INFO_FOOTER.s(), false); + return true; + } + case "l": + case "list": + if (!Permissions.hasPermission(player, C.PERMISSION_AREA_LIST)) { + C.NO_PERMISSION.send(player, C.PERMISSION_AREA_LIST); + return false; + } + int page; + switch (args.length) { + case 1: + page = 0; + break; + case 2: + if (MathMan.isInteger(args[1])) { + page = Integer.parseInt(args[1]) - 1; + break; + } + default: + C.COMMAND_SYNTAX.send(player, getCommandString() + " list [#]"); + return false; + } + ArrayList areas = new ArrayList<>(PlotSquared.get().getPlotAreas()); + paginate(player, areas, 8, page, + new RunnableVal3() { + @Override public void run(Integer i, PlotArea area, PlotMessage message) { + String name; + double percent; + int claimed = area.getPlotCount(); + int clusters = area.getClusters().size(); + String region; + String generator = String.valueOf(area.getGenerator()); + if (area.TYPE == 2) { + PlotId min = area.getMin(); + PlotId max = area.getMax(); + name = area.worldname + ';' + area.id + ';' + min + ';' + max; + int size = (max.x - min.x + 1) * (max.y - min.y + 1); + percent = claimed == 0 ? 0 : size / (double) claimed; + region = area.getRegion().toString(); + } else { + name = area.worldname; + percent = claimed == 0 ? + 0 : + Short.MAX_VALUE * Short.MAX_VALUE / (double) claimed; + region = "N/A"; + } + PlotMessage tooltip = new PlotMessage().text("Claimed=").color("$1") + .text(String.valueOf(claimed)).color("$2").text("\nUsage=") + .color("$1").text(String.format("%.2f", percent) + '%').color("$2") + .text("\nClusters=").color("$1").text(String.valueOf(clusters)) + .color("$2").text("\nRegion=").color("$1").text(region).color("$2") + .text("\nGenerator=").color("$1").text(generator).color("$2"); + + // type / terrain + String visit = "/plot area tp " + area.toString(); + message.text("[").color("$3").text(String.valueOf(i)).command(visit) + .tooltip(visit).color("$1").text("]").color("$3").text(' ' + name) + .tooltip(tooltip).command(getCommandString() + " info " + area) + .color("$1").text(" - ").color("$2") + .text(area.TYPE + ":" + area.TERRAIN).color("$3"); + } + }, "/plot area list", C.AREA_LIST_HEADER_PAGED.s()); + return true; + case "regen": + case "clear": + case "reset": + case "regenerate": { + if (!Permissions.hasPermission(player, C.PERMISSION_AREA_REGEN)) { + C.NO_PERMISSION.send(player, C.PERMISSION_AREA_REGEN); + return false; + } + final PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + if (area.TYPE != 2) { + MainUtil.sendMessage(player, + "$4Stop the server and delete: " + area.worldname + "/region"); + return false; + } + ChunkManager + .largeRegionTask(area.worldname, area.getRegion(), new RunnableVal() { + @Override public void run(ChunkLoc value) { + AugmentedUtils.generate(area.worldname, value.x, value.z, null); + } + }, () -> player.sendMessage("Regen complete")); + return true; + } + case "goto": + case "v": + case "teleport": + case "visit": + case "tp": + if (!Permissions.hasPermission(player, C.PERMISSION_AREA_TP)) { + C.NO_PERMISSION.send(player, C.PERMISSION_AREA_TP); + return false; + } + if (args.length != 2) { + C.COMMAND_SYNTAX.send(player, "/plot visit [area]"); + return false; + } + PlotArea area = PlotSquared.get().getPlotAreaByString(args[1]); + if (area == null) { + C.NOT_VALID_PLOT_WORLD.send(player, args[1]); + return false; + } + Location center; + if (area.TYPE != 2) { + center = WorldUtil.IMP.getSpawn(area.worldname); + } else { + RegionWrapper region = area.getRegion(); + center = + new Location(area.worldname, region.minX + (region.maxX - region.minX) / 2, + 0, region.minZ + (region.maxZ - region.minZ) / 2); + center.setY(1 + WorldUtil.IMP + .getHighestBlock(area.worldname, center.getX(), center.getZ())); + } + player.teleport(center); + return true; + case "delete": + case "remove": + MainUtil.sendMessage(player, + "$1World creation settings may be stored in multiple locations:" + + "\n$3 - $2Bukkit bukkit.yml" + "\n$3 - $2" + PlotSquared.imp() + .getPluginName() + " settings.yml" + + "\n$3 - $2Multiverse worlds.yml (or any world management plugin)" + + "\n$1Stop the server and delete it from these locations."); + return true; + } + C.COMMAND_SYNTAX.send(player, getUsage()); + return false; + } + +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Auto.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Auto.java new file mode 100644 index 000000000..5035c1480 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Auto.java @@ -0,0 +1,256 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.*; + +import javax.annotation.Nullable; +import java.util.Set; + +@CommandDeclaration(command = "auto", permission = "plots.auto", + category = CommandCategory.CLAIMING, requiredType = RequiredType.NONE, + description = "Claim the nearest plot", aliases = "a", usage = "/plot auto [length,width]") +public class Auto extends SubCommand { + + @Deprecated public static PlotId getNextPlotId(PlotId id, int step) { + return id.getNextId(step); + } + + private static boolean checkAllowedPlots(PlotPlayer player, PlotArea plotarea, + @Nullable Integer allowed_plots, int size_x, int size_z) { + if (allowed_plots == null) + allowed_plots = player.getAllowedPlots(); + int currentPlots = + Settings.Limit.GLOBAL ? player.getPlotCount() : player.getPlotCount(plotarea.worldname); + int diff = currentPlots - allowed_plots; + if (diff + size_x * size_z > 0) { + if (diff < 0) { + MainUtil.sendMessage(player, C.CANT_CLAIM_MORE_PLOTS_NUM, -diff + ""); + return false; + } else if (player.hasPersistentMeta("grantedPlots")) { + int grantedPlots = + ByteArrayUtilities.bytesToInteger(player.getPersistentMeta("grantedPlots")); + if (grantedPlots - diff < size_x * size_z) { + player.removePersistentMeta("grantedPlots"); + MainUtil.sendMessage(player, C.CANT_CLAIM_MORE_PLOTS); + return false; + } else { + int left = grantedPlots - diff - size_x * size_z; + if (left == 0) { + player.removePersistentMeta("grantedPlots"); + } else { + player.setPersistentMeta("grantedPlots", + ByteArrayUtilities.integerToBytes(left)); + } + MainUtil.sendMessage(player, C.REMOVED_GRANTED_PLOT, "" + left, + "" + (grantedPlots - left)); + } + } else { + MainUtil.sendMessage(player, C.CANT_CLAIM_MORE_PLOTS); + return false; + } + } + return true; + } + + /** + * Teleport the player home, or claim a new plot + * + * @param player + * @param area + * @param start + * @param schem + */ + public static void homeOrAuto(final PlotPlayer player, final PlotArea area, PlotId start, + final String schem) { + Set plots = player.getPlots(); + if (!plots.isEmpty()) { + plots.iterator().next().teleportPlayer(player); + } else { + autoClaimSafe(player, area, start, schem); + } + } + + /** + * Claim a new plot for a player + * + * @param player + * @param area + * @param start + * @param schem + */ + public static void autoClaimSafe(final PlotPlayer player, final PlotArea area, PlotId start, + final String schem) { + autoClaimSafe(player, area, start, schem, null); + } + + /** + * Claim a new plot for a player + * + * @param player + * @param area + * @param start + * @param schem + */ + public static void autoClaimSafe(final PlotPlayer player, final PlotArea area, PlotId start, + final String schem, @Nullable final Integer allowed_plots) { + player.setMeta(Auto.class.getName(), true); + autoClaimFromDatabase(player, area, start, new RunnableVal() { + @Override public void run(final Plot plot) { + TaskManager.IMP.sync(new RunnableVal() { + @Override public void run(Object ignore) { + player.deleteMeta(Auto.class.getName()); + if (plot == null) { + MainUtil.sendMessage(player, C.NO_FREE_PLOTS); + } else if (checkAllowedPlots(player, area, allowed_plots, 1, 1)) { + plot.claim(player, true, schem, false); + if (area.AUTO_MERGE) { + plot.autoMerge(-1, Integer.MAX_VALUE, player.getUUID(), true); + } + } else { + DBFunc.delete(plot); + } + } + }); + } + }); + } + + public static void autoClaimFromDatabase(final PlotPlayer player, final PlotArea area, + PlotId start, final RunnableVal whenDone) { + final Plot plot = area.getNextFreePlot(player, start); + if (plot == null) { + whenDone.run(null); + return; + } + whenDone.value = plot; + plot.owner = player.getUUID(); + DBFunc.createPlotSafe(plot, whenDone, new Runnable() { + @Override public void run() { + autoClaimFromDatabase(player, area, plot.getId(), whenDone); + } + }); + } + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + PlotArea plotarea = player.getApplicablePlotArea(); + if (plotarea == null) { + if (EconHandler.manager != null) { + for (PlotArea area : PlotSquared.get().getPlotAreaManager().getAllPlotAreas()) { + if (EconHandler.manager + .hasPermission(area.worldname, player.getName(), "plots.auto")) { + if (plotarea != null) { + plotarea = null; + break; + } + plotarea = area; + } + } + } + if (plotarea == null) { + MainUtil.sendMessage(player, C.NOT_IN_PLOT_WORLD); + return false; + } + } + int size_x = 1; + int size_z = 1; + String schematic = null; + if (args.length > 0) { + if (Permissions.hasPermission(player, C.PERMISSION_AUTO_MEGA)) { + try { + String[] split = args[0].split(",|;"); + size_x = Integer.parseInt(split[0]); + size_z = Integer.parseInt(split[1]); + if (size_x < 1 || size_z < 1) { + MainUtil.sendMessage(player, "&cError: size<=0"); + } + if (args.length > 1) { + schematic = args[1]; + } + } catch (NumberFormatException ignored) { + size_x = 1; + size_z = 1; + schematic = args[0]; + // PlayerFunctions.sendMessage(plr, + // "&cError: Invalid size (X,Y)"); + // return false; + } + } else { + schematic = args[0]; + // PlayerFunctions.sendMessage(plr, C.NO_PERMISSION); + // return false; + } + } + if (size_x * size_z > Settings.Claim.MAX_AUTO_AREA) { + MainUtil.sendMessage(player, C.CANT_CLAIM_MORE_PLOTS_NUM, + Settings.Claim.MAX_AUTO_AREA + ""); + return false; + } + final int allowed_plots = player.getAllowedPlots(); + if (player.getMeta(Auto.class.getName(), false) || !checkAllowedPlots(player, plotarea, + allowed_plots, size_x, size_z)) + return false; + + if (schematic != null && !schematic.isEmpty()) { + if (!plotarea.SCHEMATICS.contains(schematic.toLowerCase())) { + sendMessage(player, C.SCHEMATIC_INVALID, "non-existent: " + schematic); + return true; + } + if (!Permissions.hasPermission(player, C.PERMISSION_CLAIM_SCHEMATIC.f(schematic)) + && !Permissions.hasPermission(player, C.PERMISSION_ADMIN_COMMAND_SCHEMATIC)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_CLAIM_SCHEMATIC.f(schematic)); + return true; + } + } + if (EconHandler.manager != null && plotarea.USE_ECONOMY) { + Expression costExp = plotarea.PRICES.get("claim"); + double cost = costExp.evaluate((double) (Settings.Limit.GLOBAL ? + player.getPlotCount() : + player.getPlotCount(plotarea.worldname))); + cost = (size_x * size_z) * cost; + if (cost > 0d) { + if (EconHandler.manager.getMoney(player) < cost) { + sendMessage(player, C.CANNOT_AFFORD_PLOT, "" + cost); + return true; + } + EconHandler.manager.withdrawMoney(player, cost); + sendMessage(player, C.REMOVED_BALANCE, cost + ""); + } + } + // TODO handle type 2 the same as normal worlds! + if (size_x == 1 && size_z == 1) { + autoClaimSafe(player, plotarea, null, schematic, allowed_plots); + return true; + } else { + if (plotarea.TYPE == 2) { + MainUtil.sendMessage(player, C.NO_FREE_PLOTS); + return false; + } + while (true) { + PlotId start = plotarea.getMeta("lastPlot", new PlotId(0, 0)).getNextId(1); + PlotId end = new PlotId(start.x + size_x - 1, start.y + size_z - 1); + if (plotarea.canClaim(player, start, end)) { + plotarea.setMeta("lastPlot", start); + for (int i = start.x; i <= end.x; i++) { + for (int j = start.y; j <= end.y; j++) { + Plot plot = plotarea.getPlotAbs(new PlotId(i, j)); + boolean teleport = i == end.x && j == end.y; + plot.claim(player, teleport, null); + } + } + if (!plotarea.mergePlots(MainUtil.getPlotSelectionIds(start, end), true)) { + return false; + } + break; + } + plotarea.setMeta("lastPlot", start); + } + return true; + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Biome.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Biome.java new file mode 100644 index 000000000..82a573949 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Biome.java @@ -0,0 +1,38 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.StringMan; +import com.github.intellectualsites.plotsquared.plot.util.WorldUtil; + +@CommandDeclaration(command = "setbiome", permission = "plots.set.biome", + description = "Set the plot biome", usage = "/plot biome [biome]", + aliases = {"biome", "sb", "setb", "b"}, category = CommandCategory.APPEARANCE, + requiredType = RequiredType.NONE) public class Biome extends SetCommand { + + @Override public boolean set(final PlotPlayer player, final Plot plot, final String value) { + int biome = WorldUtil.IMP.getBiomeFromString(value); + if (biome == -1) { + String biomes = + StringMan.join(WorldUtil.IMP.getBiomeList(), C.BLOCK_LIST_SEPARATER.s()); + C.NEED_BIOME.send(player); + MainUtil.sendMessage(player, C.SUBCOMMAND_SET_OPTIONS_HEADER.s() + biomes); + return false; + } + if (plot.getRunning() > 0) { + MainUtil.sendMessage(player, C.WAIT_FOR_TIMER); + return false; + } + plot.addRunning(); + plot.setBiome(value.toUpperCase(), new Runnable() { + @Override public void run() { + plot.removeRunning(); + MainUtil.sendMessage(player, C.BIOME_SET_TO.s() + value.toLowerCase()); + } + }); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Buy.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Buy.java new file mode 100644 index 000000000..25f9484b1 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Buy.java @@ -0,0 +1,72 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.flag.Flags; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal2; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal3; +import com.github.intellectualsites.plotsquared.plot.util.EconHandler; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; + +import java.util.Optional; +import java.util.Set; + +@CommandDeclaration(command = "buy", description = "Buy the plot you are standing on", + usage = "/plot buy", permission = "plots.buy", category = CommandCategory.CLAIMING, + requiredType = RequiredType.NONE) public class Buy extends Command { + + public Buy() { + super(MainCommand.getInstance(), true); + } + + @Override public void execute(final PlotPlayer player, String[] args, + RunnableVal3 confirm, + final RunnableVal2 whenDone) { + check(EconHandler.manager, C.ECON_DISABLED); + final Plot plot; + if (args.length != 0) { + checkTrue(args.length == 1, C.COMMAND_SYNTAX, getUsage()); + plot = check(MainUtil.getPlotFromString(player, args[0], true), null); + } else { + plot = check(player.getCurrentPlot(), C.NOT_IN_PLOT); + } + checkTrue(plot.hasOwner(), C.PLOT_UNOWNED); + checkTrue(!plot.isOwner(player.getUUID()), C.CANNOT_BUY_OWN); + Set plots = plot.getConnectedPlots(); + checkTrue(player.getPlotCount() + plots.size() <= player.getAllowedPlots(), + C.CANT_CLAIM_MORE_PLOTS); + Optional flag = plot.getFlag(Flags.PRICE); + if (!flag.isPresent()) { + throw new CommandException(C.NOT_FOR_SALE); + } + final double price = flag.get(); + checkTrue(player.getMoney() >= price, C.CANNOT_AFFORD_PLOT); + player.withdraw(price); + confirm.run(this, new Runnable() { + @Override // Success + public void run() { + C.REMOVED_BALANCE.send(player, price); + EconHandler.manager + .depositMoney(UUIDHandler.getUUIDWrapper().getOfflinePlayer(plot.owner), price); + PlotPlayer owner = UUIDHandler.getPlayer(plot.owner); + if (owner != null) { + C.PLOT_SOLD.send(owner, plot.getId(), player.getName(), price); + } + plot.removeFlag(Flags.PRICE); + plot.setOwner(player.getUUID()); + C.CLAIMED.send(player); + whenDone.run(Buy.this, CommandResult.SUCCESS); + } + }, new Runnable() { + @Override // Failure + public void run() { + player.deposit(price); + whenDone.run(Buy.this, CommandResult.FAILURE); + } + }); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Chat.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Chat.java new file mode 100644 index 000000000..453a6a8ac --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Chat.java @@ -0,0 +1,14 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; + +@CommandDeclaration(command = "chat", description = "Toggle plot chat on or off", + usage = "/plot chat [on|off]", permission = "plots.chat", category = CommandCategory.CHAT, + requiredType = RequiredType.NONE) public class Chat extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + MainCommand.getInstance().toggle.chat(this, player, new String[0], null, null); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Claim.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Claim.java new file mode 100644 index 000000000..e49155b8d --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Claim.java @@ -0,0 +1,99 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.ByteArrayUtilities; +import com.github.intellectualsites.plotsquared.plot.util.EconHandler; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; +import com.github.intellectualsites.plotsquared.plot.util.TaskManager; + +@CommandDeclaration(command = "claim", aliases = "c", + description = "Claim the current plot you're standing on", category = CommandCategory.CLAIMING, + requiredType = RequiredType.NONE, permission = "plots.claim", usage = "/plot claim") +public class Claim extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + String schematic = ""; + if (args.length >= 1) { + schematic = args[0]; + } + Location loc = player.getLocation(); + final Plot plot = loc.getPlotAbs(); + if (plot == null) { + return sendMessage(player, C.NOT_IN_PLOT); + } + int currentPlots = + Settings.Limit.GLOBAL ? player.getPlotCount() : player.getPlotCount(loc.getWorld()); + int grants = 0; + if (currentPlots >= player.getAllowedPlots()) { + if (player.hasPersistentMeta("grantedPlots")) { + grants = + ByteArrayUtilities.bytesToInteger(player.getPersistentMeta("grantedPlots")); + if (grants <= 0) { + player.removePersistentMeta("grantedPlots"); + return sendMessage(player, C.CANT_CLAIM_MORE_PLOTS); + } + } else { + return sendMessage(player, C.CANT_CLAIM_MORE_PLOTS); + } + } + if (!plot.canClaim(player)) { + return sendMessage(player, C.PLOT_IS_CLAIMED); + } + final PlotArea area = plot.getArea(); + if (!schematic.isEmpty()) { + if (area.SCHEMATIC_CLAIM_SPECIFY) { + if (!area.SCHEMATICS.contains(schematic.toLowerCase())) { + return sendMessage(player, C.SCHEMATIC_INVALID, "non-existent: " + schematic); + } + if (!Permissions.hasPermission(player, C.PERMISSION_CLAIM_SCHEMATIC.f(schematic)) + && !Permissions.hasPermission(player, C.PERMISSION_ADMIN_COMMAND_SCHEMATIC)) { + return sendMessage(player, C.NO_SCHEMATIC_PERMISSION, schematic); + } + } + } + int border = area.getBorder(); + if (border != Integer.MAX_VALUE && plot.getDistanceFromOrigin() > border) { + return !sendMessage(player, C.BORDER); + } + if ((EconHandler.manager != null) && area.USE_ECONOMY) { + Expression costExr = area.PRICES.get("claim"); + double cost = costExr.evaluate((double) currentPlots); + if (cost > 0d) { + if (EconHandler.manager.getMoney(player) < cost) { + return sendMessage(player, C.CANNOT_AFFORD_PLOT, "" + cost); + } + EconHandler.manager.withdrawMoney(player, cost); + sendMessage(player, C.REMOVED_BALANCE, cost + ""); + } + } + if (grants > 0) { + if (grants == 1) { + player.removePersistentMeta("grantedPlots"); + } else { + player.setPersistentMeta("grantedPlots", + ByteArrayUtilities.integerToBytes(grants - 1)); + } + sendMessage(player, C.REMOVED_GRANTED_PLOT, "1", "" + (grants - 1)); + } + if (plot.canClaim(player)) { + plot.owner = player.getUUID(); + final String finalSchematic = schematic; + DBFunc.createPlotSafe(plot, () -> TaskManager.IMP.sync(new RunnableVal() { + @Override public void run(Object value) { + plot.claim(player, true, finalSchematic, false); + if (area.AUTO_MERGE) { + plot.autoMerge(-1, Integer.MAX_VALUE, player.getUUID(), true); + } + } + }), () -> sendMessage(player, C.PLOT_NOT_CLAIMED)); + return true; + } else { + sendMessage(player, C.PLOT_NOT_CLAIMED); + } + return false; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Clear.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Clear.java new file mode 100644 index 000000000..efd6b1525 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Clear.java @@ -0,0 +1,68 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.flag.FlagManager; +import com.github.intellectualsites.plotsquared.plot.flag.Flags; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal2; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal3; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; +import com.github.intellectualsites.plotsquared.plot.util.block.GlobalBlockQueue; + +@CommandDeclaration(command = "clear", description = "Clear the plot you stand on", + permission = "plots.clear", category = CommandCategory.APPEARANCE, usage = "/plot clear", + aliases = "reset", confirmation = true) public class Clear extends Command { + + // Note: To clear a specific plot use /plot clear + // The syntax also works with any command: /plot + + public Clear() { + super(MainCommand.getInstance(), true); + } + + @Override public void execute(final PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) throws CommandException { + checkTrue(args.length == 0, C.COMMAND_SYNTAX, getUsage()); + final Plot plot = check(player.getCurrentPlot(), C.NOT_IN_PLOT); + checkTrue(plot.isOwner(player.getUUID()) || Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_CLEAR), C.NO_PLOT_PERMS); + checkTrue(plot.getRunning() == 0, C.WAIT_FOR_TIMER); + checkTrue(!Settings.Done.RESTRICT_BUILDING || !Flags.DONE.isSet(plot) || Permissions + .hasPermission(player, C.PERMISSION_CONTINUE), C.DONE_ALREADY_DONE); + confirm.run(this, new Runnable() { + @Override public void run() { + final long start = System.currentTimeMillis(); + boolean result = plot.clear(true, false, new Runnable() { + @Override public void run() { + plot.unlink(); + GlobalBlockQueue.IMP.addTask(new Runnable() { + @Override public void run() { + plot.removeRunning(); + // If the state changes, then mark it as no longer done + if (plot.getFlag(Flags.DONE).isPresent()) { + FlagManager.removePlotFlag(plot, Flags.DONE); + } + if (plot.getFlag(Flags.ANALYSIS).isPresent()) { + FlagManager.removePlotFlag(plot, Flags.ANALYSIS); + } + MainUtil.sendMessage(player, C.CLEARING_DONE, + "" + (System.currentTimeMillis() - start)); + } + }); + } + }); + if (!result) { + MainUtil.sendMessage(player, C.WAIT_FOR_TIMER); + } else { + plot.addRunning(); + } + } + }, null); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Cluster.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Cluster.java new file mode 100644 index 000000000..b4b24021f --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Cluster.java @@ -0,0 +1,612 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@CommandDeclaration(command = "cluster", aliases = "clusters", + category = CommandCategory.ADMINISTRATION, requiredType = RequiredType.NONE, + permission = "plots.cluster", description = "Manage a plot cluster") public class Cluster + extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + + // list, create, delete, resize, invite, kick, leave, helpers, tp, sethome + if (args.length == 0) { + // return arguments + MainUtil.sendMessage(player, C.CLUSTER_AVAILABLE_ARGS); + return false; + } + String sub = args[0].toLowerCase(); + switch (sub) { + case "l": + case "list": { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_LIST)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_LIST); + return false; + } + if (args.length != 1) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot cluster list"); + return false; + } + PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + Set clusters = area.getClusters(); + MainUtil.sendMessage(player, C.CLUSTER_LIST_HEADING, clusters.size() + ""); + for (PlotCluster cluster : clusters) { + // Ignore unmanaged clusters + String name = "'" + cluster.getName() + "' : " + cluster.toString(); + if (player.getUUID().equals(cluster.owner)) { + MainUtil.sendMessage(player, C.CLUSTER_LIST_ELEMENT, "&a" + name); + } else if (cluster.helpers.contains(player.getUUID())) { + MainUtil.sendMessage(player, C.CLUSTER_LIST_ELEMENT, "&3" + name); + } else if (cluster.invited.contains(player.getUUID())) { + MainUtil.sendMessage(player, C.CLUSTER_LIST_ELEMENT, "&9" + name); + } else { + MainUtil.sendMessage(player, C.CLUSTER_LIST_ELEMENT, cluster.toString()); + } + } + return true; + } + case "c": + case "create": { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_CREATE)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_CREATE); + return false; + } + PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + if (args.length != 4) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, + "/plot cluster create "); + return false; + } + int currentClusters = Settings.Limit.GLOBAL ? + player.getClusterCount() : + player.getPlotCount(player.getLocation().getWorld()); + if (currentClusters >= player.getAllowedPlots()) { + return sendMessage(player, C.CANT_CLAIM_MORE_CLUSTERS); + } + // check pos1 / pos2 + PlotId pos1 = PlotId.fromString(args[2]); + PlotId pos2 = PlotId.fromString(args[3]); + if (pos1 == null || pos2 == null) { + MainUtil.sendMessage(player, C.NOT_VALID_PLOT_ID); + return false; + } + // check if name is taken + String name = args[1]; + if (area.getCluster(name) != null) { + MainUtil.sendMessage(player, C.ALIAS_IS_TAKEN); + return false; + } + if (pos2.x < pos1.x || pos2.y < pos1.y) { + PlotId tmp = new PlotId(Math.min(pos1.x, pos2.x), Math.min(pos1.y, pos2.y)); + pos2 = new PlotId(Math.max(pos1.x, pos2.x), Math.max(pos1.y, pos2.y)); + pos1 = tmp; + } + //check if overlap + PlotCluster cluster = area.getFirstIntersectingCluster(pos1, pos2); + if (cluster != null) { + MainUtil.sendMessage(player, C.CLUSTER_INTERSECTION, cluster.getName()); + return false; + } + // Check if it occupies existing plots + if (!area.contains(pos1) || !area.contains(pos2)) { + C.CLUSTER_OUTSIDE.send(player, area); + return false; + } + Set plots = area.getPlotSelectionOwned(pos1, pos2); + if (!plots.isEmpty()) { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_CREATE_OTHER)) { + UUID uuid = player.getUUID(); + for (Plot plot : plots) { + if (!plot.isOwner(uuid)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_CLUSTER_CREATE_OTHER); + return false; + } + } + } + } + // Check allowed cluster size + cluster = new PlotCluster(area, pos1, pos2, player.getUUID()); + int current; + if (Settings.Limit.GLOBAL) { + current = player.getPlayerClusterCount(); + } else { + current = player.getPlayerClusterCount(player.getLocation().getWorld()); + } + int allowed = Permissions.hasPermissionRange(player, C.PERMISSION_CLUSTER_SIZE, + Settings.Limit.MAX_PLOTS); + if (current + cluster.getArea() > allowed) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_CLUSTER_SIZE + "." + (current + cluster.getArea())); + return false; + } + // create cluster + cluster.settings.setAlias(name); + area.addCluster(cluster); + DBFunc.createCluster(cluster); + // Add any existing plots to the current cluster + for (Plot plot : plots) { + if (plot.hasOwner()) { + if (!cluster.isAdded(plot.owner)) { + cluster.invited.add(plot.owner); + DBFunc.setInvited(cluster, plot.owner); + } + } + } + MainUtil.sendMessage(player, C.CLUSTER_ADDED); + return true; + } + case "disband": + case "del": + case "delete": { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_DELETE)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_DELETE); + return false; + } + if (args.length != 1 && args.length != 2) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot cluster delete [name]"); + return false; + } + PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + PlotCluster cluster; + if (args.length == 2) { + cluster = area.getCluster(args[1]); + if (cluster == null) { + MainUtil.sendMessage(player, C.INVALID_CLUSTER, args[1]); + return false; + } + } else { + cluster = area.getCluster(player.getLocation()); + if (cluster == null) { + MainUtil.sendMessage(player, C.NOT_IN_CLUSTER); + return false; + } + } + if (!cluster.owner.equals(player.getUUID())) { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_DELETE_OTHER)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_CLUSTER_DELETE_OTHER); + return false; + } + } + DBFunc.delete(cluster); + MainUtil.sendMessage(player, C.CLUSTER_DELETED); + return true; + } + case "res": + case "resize": { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_RESIZE)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_RESIZE); + return false; + } + if (args.length != 3) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, + "/plot cluster resize "); + return false; + } + // check pos1 / pos2 + PlotId pos1 = PlotId.fromString(args[1]); + PlotId pos2 = PlotId.fromString(args[2]); + if (pos1 == null || pos2 == null) { + MainUtil.sendMessage(player, C.NOT_VALID_PLOT_ID); + return false; + } + if (pos2.x < pos1.x || pos2.y < pos1.y) { + pos1 = new PlotId(Math.min(pos1.x, pos2.x), Math.min(pos1.y, pos2.y)); + pos2 = new PlotId(Math.max(pos1.x, pos2.x), Math.max(pos1.y, pos2.y)); + } + // check if in cluster + PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + PlotCluster cluster = area.getCluster(player.getLocation()); + if (cluster == null) { + MainUtil.sendMessage(player, C.NOT_IN_CLUSTER); + return false; + } + if (!cluster.hasHelperRights(player.getUUID())) { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_RESIZE_OTHER)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_CLUSTER_RESIZE_OTHER); + return false; + } + } + //check if overlap + PlotCluster intersect = area.getFirstIntersectingCluster(pos1, pos2); + if (intersect != null) { + MainUtil.sendMessage(player, C.CLUSTER_INTERSECTION, intersect.getName()); + return false; + } + Set existing = area.getPlotSelectionOwned(cluster.getP1(), cluster.getP2()); + Set newPlots = area.getPlotSelectionOwned(pos1, pos2); + // Set removed = (HashSet) existing.clone(); + Set removed = new HashSet<>(existing); + + removed.removeAll(newPlots); + // Check expand / shrink + if (!removed.isEmpty()) { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_RESIZE_SHRINK)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_CLUSTER_RESIZE_SHRINK); + return false; + } + } + newPlots.removeAll(existing); + if (!newPlots.isEmpty()) { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_RESIZE_EXPAND)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_CLUSTER_RESIZE_EXPAND); + return false; + } + } + // Check allowed cluster size + int current; + if (Settings.Limit.GLOBAL) { + current = player.getPlayerClusterCount(); + } else { + current = player.getPlayerClusterCount(player.getLocation().getWorld()); + } + current -= cluster.getArea() + (1 + pos2.x - pos1.x) * (1 + pos2.y - pos1.y); + int allowed = Permissions + .hasPermissionRange(player, C.PERMISSION_CLUSTER, Settings.Limit.MAX_PLOTS); + if (current + cluster.getArea() > allowed) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_CLUSTER.s() + "." + (current + cluster.getArea())); + return false; + } + // resize cluster + DBFunc.resizeCluster(cluster, pos1, pos2); + MainUtil.sendMessage(player, C.CLUSTER_RESIZED); + return true; + } + case "add": + case "inv": + case "invite": { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_INVITE)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_INVITE); + return false; + } + if (args.length != 2) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot cluster invite "); + return false; + } + // check if in cluster + PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + PlotCluster cluster = area.getCluster(player.getLocation()); + if (cluster == null) { + MainUtil.sendMessage(player, C.NOT_IN_CLUSTER); + return false; + } + if (!cluster.hasHelperRights(player.getUUID())) { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_INVITE_OTHER)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_CLUSTER_INVITE_OTHER); + return false; + } + } + // check uuid + UUID uuid = UUIDHandler.getUUID(args[1], null); + if (uuid == null) { + MainUtil.sendMessage(player, C.INVALID_PLAYER, args[2]); + return false; + } + if (!cluster.isAdded(uuid)) { + // add the user if not added + cluster.invited.add(uuid); + DBFunc.setInvited(cluster, uuid); + PlotPlayer player2 = UUIDHandler.getPlayer(uuid); + if (player2 != null) { + MainUtil.sendMessage(player2, C.CLUSTER_INVITED, cluster.getName()); + } + } + MainUtil.sendMessage(player, C.CLUSTER_ADDED_USER); + return true; + } + case "k": + case "remove": + case "kick": { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_KICK)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_KICK); + return false; + } + if (args.length != 2) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot cluster kick "); + return false; + } + PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + PlotCluster cluster = area.getCluster(player.getLocation()); + if (cluster == null) { + MainUtil.sendMessage(player, C.NOT_IN_CLUSTER); + return false; + } + if (!cluster.hasHelperRights(player.getUUID())) { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_KICK_OTHER)) { + MainUtil + .sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_KICK_OTHER); + return false; + } + } + // check uuid + UUID uuid = UUIDHandler.getUUID(args[1], null); + if (uuid == null) { + MainUtil.sendMessage(player, C.INVALID_PLAYER, args[1]); + return false; + } + // Can't kick if the player is yourself, the owner, or not added to the cluster + if (uuid.equals(player.getUUID()) || uuid.equals(cluster.owner) || !cluster + .isAdded(uuid)) { + MainUtil.sendMessage(player, C.CANNOT_KICK_PLAYER, cluster.getName()); + return false; + } + if (cluster.helpers.contains(uuid)) { + cluster.helpers.remove(uuid); + DBFunc.removeHelper(cluster, uuid); + } + cluster.invited.remove(uuid); + DBFunc.removeInvited(cluster, uuid); + PlotPlayer player2 = UUIDHandler.getPlayer(uuid); + if (player2 != null) { + MainUtil.sendMessage(player2, C.CLUSTER_REMOVED, cluster.getName()); + } + for (Plot plot : new ArrayList<>( + PlotSquared.get().getPlots(player2.getLocation().getWorld(), uuid))) { + PlotCluster current = plot.getCluster(); + if (current != null && current.equals(cluster)) { + plot.unclaim(); + } + } + MainUtil.sendMessage(player2, C.CLUSTER_KICKED_USER); + return true; + } + case "quit": + case "leave": { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_LEAVE)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_LEAVE); + return false; + } + if (args.length != 1 && args.length != 2) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot cluster leave [name]"); + return false; + } + PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + PlotCluster cluster; + if (args.length == 2) { + cluster = area.getCluster(args[1]); + if (cluster == null) { + MainUtil.sendMessage(player, C.INVALID_CLUSTER, args[1]); + return false; + } + } else { + cluster = area.getCluster(player.getLocation()); + if (cluster == null) { + MainUtil.sendMessage(player, C.NOT_IN_CLUSTER); + return false; + } + } + UUID uuid = player.getUUID(); + if (!cluster.isAdded(uuid)) { + MainUtil.sendMessage(player, C.CLUSTER_NOT_ADDED); + return false; + } + if (uuid.equals(cluster.owner)) { + MainUtil.sendMessage(player, C.CLUSTER_CANNOT_LEAVE); + return false; + } + if (cluster.helpers.contains(uuid)) { + cluster.helpers.remove(uuid); + DBFunc.removeHelper(cluster, uuid); + } + cluster.invited.remove(uuid); + DBFunc.removeInvited(cluster, uuid); + MainUtil.sendMessage(player, C.CLUSTER_REMOVED, cluster.getName()); + for (Plot plot : new ArrayList<>( + PlotSquared.get().getPlots(player.getLocation().getWorld(), uuid))) { + PlotCluster current = plot.getCluster(); + if (current != null && current.equals(cluster)) { + plot.unclaim(); + } + } + return true; + } + case "members": + case "admin": + case "helper": + case "helpers": { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_HELPERS)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_HELPERS); + return false; + } + if (args.length != 3) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, + "/plot cluster helpers "); + return false; + } + PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + PlotCluster cluster = area.getCluster(player.getLocation()); + if (cluster == null) { + MainUtil.sendMessage(player, C.NOT_IN_CLUSTER); + return false; + } + UUID uuid = UUIDHandler.getUUID(args[2], null); + if (uuid == null) { + MainUtil.sendMessage(player, C.INVALID_PLAYER, args[2]); + return false; + } + if (args[1].equalsIgnoreCase("add")) { + cluster.helpers.add(uuid); + DBFunc.setHelper(cluster, uuid); + return MainUtil.sendMessage(player, C.CLUSTER_ADDED_HELPER); + } + if (args[1].equalsIgnoreCase("remove")) { + cluster.helpers.remove(uuid); + DBFunc.removeHelper(cluster, uuid); + return MainUtil.sendMessage(player, C.CLUSTER_REMOVED_HELPER); + } + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, + "/plot cluster helpers "); + return false; + } + case "spawn": + case "home": + case "tp": { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_TP)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_TP); + return false; + } + if (args.length != 2) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot cluster tp "); + return false; + } + PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + PlotCluster cluster = area.getCluster(args[1]); + if (cluster == null) { + MainUtil.sendMessage(player, C.INVALID_CLUSTER, args[1]); + return false; + } + UUID uuid = player.getUUID(); + if (!cluster.isAdded(uuid)) { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_TP_OTHER)) { + MainUtil + .sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_TP_OTHER); + return false; + } + } + player.teleport(cluster.getHome()); + return MainUtil.sendMessage(player, C.CLUSTER_TELEPORTING); + } + case "i": + case "info": + case "show": + case "information": { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_INFO)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_INFO); + return false; + } + if (args.length != 1 && args.length != 2) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot cluster info [name]"); + return false; + } + PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + PlotCluster cluster; + if (args.length == 2) { + cluster = area.getCluster(args[1]); + if (cluster == null) { + MainUtil.sendMessage(player, C.INVALID_CLUSTER, args[1]); + return false; + } + } else { + cluster = area.getCluster(player.getLocation()); + if (cluster == null) { + MainUtil.sendMessage(player, C.NOT_IN_CLUSTER); + return false; + } + } + String id = cluster.toString(); + String owner = UUIDHandler.getName(cluster.owner); + if (owner == null) { + owner = "unknown"; + } + String name = cluster.getName(); + String size = (cluster.getP2().x - cluster.getP1().x + 1) + "x" + ( + cluster.getP2().y - cluster.getP1().y + 1); + String rights = cluster.isAdded(player.getUUID()) + ""; + String message = C.CLUSTER_INFO.s(); + message = message.replaceAll("%id%", id); + message = message.replaceAll("%owner%", owner); + message = message.replaceAll("%name%", name); + message = message.replaceAll("%size%", size); + message = message.replaceAll("%rights%", rights); + MainUtil.sendMessage(player, message); + return true; + } + case "sh": + case "setspawn": + case "sethome": + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_SETHOME)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_CLUSTER_SETHOME); + return false; + } + if (args.length != 1 && args.length != 2) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot cluster sethome"); + return false; + } + PlotArea area = player.getApplicablePlotArea(); + if (area == null) { + C.NOT_IN_PLOT_WORLD.send(player); + return false; + } + PlotCluster cluster = area.getCluster(player.getLocation()); + if (cluster == null) { + MainUtil.sendMessage(player, C.NOT_IN_CLUSTER); + return false; + } + if (!cluster.hasHelperRights(player.getUUID())) { + if (!Permissions.hasPermission(player, C.PERMISSION_CLUSTER_SETHOME_OTHER)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_CLUSTER_SETHOME_OTHER); + return false; + } + } + Location base = cluster.getClusterBottom(); + Location relative = player.getLocation().subtract(base.getX(), 0, base.getZ()); + BlockLoc blockloc = new BlockLoc(relative.getX(), relative.getY(), relative.getZ()); + cluster.settings.setPosition(blockloc); + DBFunc.setPosition(cluster, + relative.getX() + "," + relative.getY() + "," + relative.getZ()); + return MainUtil.sendMessage(player, C.POSITION_SET); + } + MainUtil.sendMessage(player, C.CLUSTER_AVAILABLE_ARGS); + return false; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/CommandCategory.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/CommandCategory.java new file mode 100644 index 000000000..4aeacf85c --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/CommandCategory.java @@ -0,0 +1,56 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +/** + * CommandCategory. + */ +public enum CommandCategory { + /** + * Claiming CommandConfig. + * Such as: /plot claim + */ + CLAIMING("Claiming"), /** + * Teleportation CommandConfig. + * Such as: /plot visit + */ + TELEPORT("Teleport"), /** + * Protection. + */ + SETTINGS("Protection"), /** + * Chat. + */ + CHAT("Chat"), /** + * Web. + */ + SCHEMATIC("Web"), /** + * Cosmetic. + */ + APPEARANCE("Cosmetic"), /** + * Information CommandConfig. + * Such as: /plot info + */ + INFO("Info"), /** + * Debug CommandConfig. + * Such as: /plot debug + */ + DEBUG("Debug"), /** + * Administration commands. + */ + ADMINISTRATION("Admin"); + /** + * The category name (Readable). + */ + private final String name; + + /** + * Constructor. + * + * @param name readable name + */ + CommandCategory(String name) { + this.name = name; + } + + @Override public String toString() { + return this.name; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Comment.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Comment.java new file mode 100644 index 000000000..775450517 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Comment.java @@ -0,0 +1,74 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotId; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.comment.CommentInbox; +import com.github.intellectualsites.plotsquared.plot.object.comment.PlotComment; +import com.github.intellectualsites.plotsquared.plot.util.CommentManager; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.StringMan; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; + +import java.util.Arrays; +import java.util.Map.Entry; + +@CommandDeclaration(command = "comment", aliases = {"msg"}, description = "Comment on a plot", + category = CommandCategory.CHAT, requiredType = RequiredType.NONE, permission = "plots.comment") +public class Comment extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + if (args.length < 2) { + sendMessage(player, C.COMMENT_SYNTAX, + StringMan.join(CommentManager.inboxes.keySet(), "|")); + return false; + } + CommentInbox inbox = CommentManager.inboxes.get(args[0].toLowerCase()); + if (inbox == null) { + sendMessage(player, C.COMMENT_SYNTAX, + StringMan.join(CommentManager.inboxes.keySet(), "|")); + return false; + } + Location loc = player.getLocation(); + PlotId id = PlotId.fromString(args[1]); + Plot plot = MainUtil.getPlotFromString(player, args[1], false); + int index; + if (plot == null) { + index = 1; + plot = loc.getPlotAbs(); + } else { + if (args.length < 4) { + sendMessage(player, C.COMMENT_SYNTAX, + StringMan.join(CommentManager.inboxes.keySet(), "|")); + return false; + } + index = 2; + } + if (!inbox.canWrite(plot, player)) { + sendMessage(player, C.NO_PERM_INBOX, ""); + return false; + } + String message = StringMan.join(Arrays.copyOfRange(args, index, args.length), " "); + PlotComment comment = + new PlotComment(loc.getWorld(), id, message, player.getName(), inbox.toString(), + System.currentTimeMillis()); + boolean result = inbox.addComment(plot, comment); + if (!result) { + sendMessage(player, C.NO_PLOT_INBOX, ""); + sendMessage(player, C.COMMENT_SYNTAX, + StringMan.join(CommentManager.inboxes.keySet(), "|")); + return false; + } + for (Entry entry : UUIDHandler.getPlayers().entrySet()) { + PlotPlayer pp = entry.getValue(); + if (pp.getAttribute("chatspy")) { + MainUtil.sendMessage(pp, "/plot comment " + StringMan.join(args, " ")); + } + } + sendMessage(player, C.COMMENT_ADDED); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Condense.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Condense.java new file mode 100644 index 000000000..44d0e5593 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Condense.java @@ -0,0 +1,209 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotArea; +import com.github.intellectualsites.plotsquared.plot.object.PlotId; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.MathMan; +import com.github.intellectualsites.plotsquared.plot.util.TaskManager; +import com.github.intellectualsites.plotsquared.plot.util.WorldUtil; + +import java.util.*; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +@CommandDeclaration(command = "condense", permission = "plots.admin", + description = "Condense a plotworld", category = CommandCategory.ADMINISTRATION, + requiredType = RequiredType.CONSOLE) public class Condense extends SubCommand { + + public static boolean TASK = false; + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + if (args.length != 2 && args.length != 3) { + MainUtil.sendMessage(player, "/plot condense [radius]"); + return false; + } + PlotArea area = PlotSquared.get().getPlotAreaByString(args[0]); + if (area == null || !WorldUtil.IMP.isWorld(area.worldname)) { + MainUtil.sendMessage(player, "INVALID AREA"); + return false; + } + switch (args[1].toLowerCase()) { + case "start": { + if (args.length == 2) { + MainUtil.sendMessage(player, + "/plot condense " + area.toString() + " start "); + return false; + } + if (Condense.TASK) { + MainUtil.sendMessage(player, "TASK ALREADY STARTED"); + return false; + } + if (!MathMan.isInteger(args[2])) { + MainUtil.sendMessage(player, "INVALID RADIUS"); + return false; + } + int radius = Integer.parseInt(args[2]); + ArrayList plots = new ArrayList<>(PlotSquared.get().getPlots(area)); + // remove non base plots + Iterator iterator = plots.iterator(); + int maxSize = 0; + ArrayList sizes = new ArrayList<>(); + while (iterator.hasNext()) { + Plot plot = iterator.next(); + if (!plot.isBasePlot()) { + iterator.remove(); + continue; + } + int size = plot.getConnectedPlots().size(); + if (size > maxSize) { + maxSize = size; + } + sizes.add(size - 1); + } + // Sort plots by size (buckets?)] + //noinspection unchecked + ArrayList[] buckets = new ArrayList[maxSize]; + for (int i = 0; i < plots.size(); i++) { + Plot plot = plots.get(i); + int size = sizes.get(i); + ArrayList array = buckets[size]; + if (array == null) { + array = new ArrayList<>(); + buckets[size] = array; + } + array.add(plot); + } + final ArrayList allPlots = new ArrayList<>(plots.size()); + for (int i = buckets.length - 1; i >= 0; i--) { + ArrayList array = buckets[i]; + if (array != null) { + allPlots.addAll(array); + } + } + int size = allPlots.size(); + int minimumRadius = (int) Math.ceil(Math.sqrt(size) / 2 + 1); + if (radius < minimumRadius) { + MainUtil.sendMessage(player, "RADIUS TOO SMALL"); + return false; + } + List toMove = new ArrayList<>(getPlots(allPlots, radius)); + final List free = new ArrayList<>(); + PlotId start = new PlotId(0, 0); + while (start.x <= minimumRadius && start.y <= minimumRadius) { + Plot plot = area.getPlotAbs(start); + if (plot != null && !plot.hasOwner()) { + free.add(plot.getId()); + } + start = Auto.getNextPlotId(start, 1); + } + if (free.isEmpty() || toMove.isEmpty()) { + MainUtil.sendMessage(player, "NO FREE PLOTS FOUND"); + return false; + } + MainUtil.sendMessage(player, "TASK STARTED..."); + Runnable run = new Runnable() { + @Override public void run() { + if (!Condense.TASK) { + MainUtil.sendMessage(player, "TASK CANCELLED."); + } + if (allPlots.isEmpty()) { + Condense.TASK = false; + MainUtil.sendMessage(player, + "TASK COMPLETE. PLEASE VERIFY THAT NO NEW PLOTS HAVE BEEN CLAIMED DURING TASK."); + return; + } + final Runnable task = this; + final Plot origin = allPlots.remove(0); + int i = 0; + while (free.size() > i) { + final Plot possible = origin.getArea().getPlotAbs(free.get(i)); + if (possible.hasOwner()) { + free.remove(i); + continue; + } + i++; + final AtomicBoolean result = new AtomicBoolean(false); + result.set(origin.move(possible, () -> { + if (result.get()) { + MainUtil.sendMessage(player, + "Moving: " + origin + " -> " + possible); + TaskManager.runTaskLater(task, 1); + } + }, false)); + if (result.get()) { + break; + } + } + if (free.isEmpty()) { + Condense.TASK = false; + MainUtil.sendMessage(player, "TASK FAILED. NO FREE PLOTS FOUND!"); + return; + } + if (i >= free.size()) { + MainUtil.sendMessage(player, "SKIPPING COMPLEX PLOT: " + origin); + } + } + }; + Condense.TASK = true; + TaskManager.runTaskAsync(run); + return true; + } + case "stop": + if (!Condense.TASK) { + MainUtil.sendMessage(player, "TASK ALREADY STOPPED"); + return false; + } + Condense.TASK = false; + MainUtil.sendMessage(player, "TASK STOPPED"); + return true; + case "info": + if (args.length == 2) { + MainUtil.sendMessage(player, + "/plot condense " + area.toString() + " info "); + return false; + } + if (!MathMan.isInteger(args[2])) { + MainUtil.sendMessage(player, "INVALID RADIUS"); + return false; + } + int radius = Integer.parseInt(args[2]); + Collection plots = area.getPlots(); + int size = plots.size(); + int minimumRadius = (int) Math.ceil(Math.sqrt(size) / 2 + 1); + if (radius < minimumRadius) { + MainUtil.sendMessage(player, "RADIUS TOO SMALL"); + return false; + } + int maxMove = getPlots(plots, minimumRadius).size(); + int userMove = getPlots(plots, radius).size(); + MainUtil.sendMessage(player, "=== DEFAULT EVAL ==="); + MainUtil.sendMessage(player, "MINIMUM RADIUS: " + minimumRadius); + MainUtil.sendMessage(player, "MAXIMUM MOVES: " + maxMove); + MainUtil.sendMessage(player, "=== INPUT EVAL ==="); + MainUtil.sendMessage(player, "INPUT RADIUS: " + radius); + MainUtil.sendMessage(player, "ESTIMATED MOVES: " + userMove); + MainUtil.sendMessage(player, + "ESTIMATED TIME: No idea, times will drastically change based on the system performance and load"); + MainUtil.sendMessage(player, "&e - Radius is measured in plot width"); + return true; + } + MainUtil.sendMessage(player, + "/plot condense " + area.worldname + " [radius]"); + return false; + } + + public Set getPlots(Collection plots, int radius) { + HashSet outside = new HashSet<>(); + for (Plot plot : plots) { + if (plot.getId().x > radius || plot.getId().x < -radius || plot.getId().y > radius + || plot.getId().y < -radius) { + outside.add(plot.getId()); + } + } + return outside; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Confirm.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Confirm.java new file mode 100644 index 000000000..b33bd4dab --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Confirm.java @@ -0,0 +1,31 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.object.CmdInstance; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.CmdConfirm; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.TaskManager; + +@CommandDeclaration(command = "confirm", permission = "plots.use", + description = "Confirm an action", category = CommandCategory.INFO) public class Confirm + extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + CmdInstance command = CmdConfirm.getPending(player); + if (command == null) { + MainUtil.sendMessage(player, C.FAILED_CONFIRM); + return false; + } + CmdConfirm.removePending(player); + if ((System.currentTimeMillis() - command.timestamp) + > Settings.Confirmation.CONFIRMATION_TIMEOUT_SECONDS * 1000) { + MainUtil.sendMessage(player, C.EXPIRED_CONFIRM); + return false; + } + TaskManager.runTask(command.command); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Continue.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Continue.java new file mode 100644 index 000000000..68bf8c786 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Continue.java @@ -0,0 +1,45 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.flag.Flags; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; + +@CommandDeclaration(command = "continue", + description = "Continue a plot that was previously marked as done", + permission = "plots.continue", category = CommandCategory.SETTINGS, + requiredType = RequiredType.NONE) public class Continue extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + Plot plot = player.getCurrentPlot(); + if ((plot == null) || !plot.hasOwner()) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_CONTINUE)) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + if (!plot.hasFlag(Flags.DONE)) { + MainUtil.sendMessage(player, C.DONE_NOT_DONE); + return false; + } + int size = plot.getConnectedPlots().size(); + if (Settings.Done.COUNTS_TOWARDS_LIMIT && (player.getAllowedPlots() + < player.getPlotCount() + size)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_ADMIN_COMMAND_CONTINUE); + return false; + } + if (plot.getRunning() > 0) { + MainUtil.sendMessage(player, C.WAIT_FOR_TIMER); + return false; + } + plot.removeFlag(Flags.DONE); + MainUtil.sendMessage(player, C.DONE_REMOVED); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Copy.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Copy.java new file mode 100644 index 000000000..8303ef042 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Copy.java @@ -0,0 +1,54 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; + +@CommandDeclaration(command = "copy", permission = "plots.copy", aliases = {"copypaste"}, + category = CommandCategory.CLAIMING, description = "Copy a plot", usage = "/plot copy ", + requiredType = RequiredType.NONE) public class Copy extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + Location loc = player.getLocation(); + Plot plot1 = loc.getPlotAbs(); + if (plot1 == null) { + return !MainUtil.sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot1.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN.s())) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + if (args.length != 1) { + C.COMMAND_SYNTAX.send(player, getUsage()); + return false; + } + Plot plot2 = MainUtil.getPlotFromString(player, args[0], true); + if (plot2 == null) { + return false; + } + if (plot1.equals(plot2)) { + MainUtil.sendMessage(player, C.NOT_VALID_PLOT_ID); + C.COMMAND_SYNTAX.send(player, getUsage()); + return false; + } + if (!plot1.getArea().isCompatible(plot2.getArea())) { + C.PLOTWORLD_INCOMPATIBLE.send(player); + return false; + } + if (plot1.copy(plot2, new Runnable() { + @Override public void run() { + MainUtil.sendMessage(player, C.COPY_SUCCESS); + } + })) { + return true; + } else { + MainUtil.sendMessage(player, C.REQUIRES_UNOWNED); + return false; + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/CreateRoadSchematic.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/CreateRoadSchematic.java new file mode 100644 index 000000000..acc47a64e --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/CreateRoadSchematic.java @@ -0,0 +1,32 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.generator.HybridPlotWorld; +import com.github.intellectualsites.plotsquared.plot.generator.HybridUtils; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; + +@CommandDeclaration(command = "createroadschematic", aliases = {"crs"}, + category = CommandCategory.ADMINISTRATION, requiredType = RequiredType.NONE, + permission = "plots.createroadschematic", + description = "Add a road schematic to your world using the roads around your current plot", + usage = "/plot createroadschematic") public class CreateRoadSchematic extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + Location loc = player.getLocation(); + Plot plot = loc.getPlotAbs(); + if (plot == null) { + return sendMessage(player, C.NOT_IN_PLOT); + } + if (!(loc.getPlotArea() instanceof HybridPlotWorld)) { + return sendMessage(player, C.NOT_IN_PLOT_WORLD); + } + HybridUtils.manager.setupRoadSchematic(plot); + MainUtil.sendMessage(player, + "$1Saved new road schematic. To test the road, fly to a few other plots and use /plot debugroadregen"); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Database.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Database.java new file mode 100644 index 000000000..e94850121 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Database.java @@ -0,0 +1,184 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.database.MySQL; +import com.github.intellectualsites.plotsquared.plot.database.SQLManager; +import com.github.intellectualsites.plotsquared.plot.database.SQLite; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotArea; +import com.github.intellectualsites.plotsquared.plot.object.PlotId; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.worlds.SinglePlotArea; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.TaskManager; + +import java.io.File; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; + +@CommandDeclaration(command = "database", aliases = {"convert"}, + category = CommandCategory.ADMINISTRATION, permission = "plots.database", + description = "Convert/Backup Storage", requiredType = RequiredType.CONSOLE, + usage = "/plot database [area] ") public class Database + extends SubCommand { + + public static void insertPlots(final SQLManager manager, final List plots, + final PlotPlayer player) { + TaskManager.runTaskAsync(() -> { + try { + ArrayList ps = new ArrayList<>(plots); + MainUtil.sendMessage(player, "&6Starting..."); + manager.createPlotsAndData(ps, () -> { + MainUtil.sendMessage(player, "&6Database conversion finished!"); + manager.close(); + }); + } catch (Exception e) { + MainUtil + .sendMessage(player, "Failed to insert plot objects, see stacktrace for info"); + e.printStackTrace(); + } + }); + } + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + if (args.length < 1) { + MainUtil.sendMessage(player, "/plot database [area] "); + return false; + } + List plots; + PlotArea area = PlotSquared.get().getPlotAreaByString(args[0]); + if (area != null) { + plots = PlotSquared.get().sortPlotsByTemp(area.getPlots()); + args = Arrays.copyOfRange(args, 1, args.length); + } else { + plots = PlotSquared.get().sortPlotsByTemp(PlotSquared.get().getPlots()); + } + if (args.length < 1) { + MainUtil.sendMessage(player, "/plot database [world] "); + MainUtil.sendMessage(player, "[arg] indicates an optional argument"); + return false; + } + try { + com.github.intellectualsites.plotsquared.plot.database.Database implementation; + String prefix = ""; + switch (args[0].toLowerCase()) { + case "import": + if (args.length < 2) { + MainUtil + .sendMessage(player, "/plot database import [prefix]"); + return false; + } + File file = MainUtil.getFile(PlotSquared.get().IMP.getDirectory(), + args[1].endsWith(".db") ? args[1] : args[1] + ".db"); + if (!file.exists()) { + MainUtil.sendMessage(player, "&6Database does not exist: " + file); + return false; + } + MainUtil.sendMessage(player, "&6Starting..."); + implementation = new SQLite(file); + SQLManager manager = + new SQLManager(implementation, args.length == 3 ? args[2] : "", true); + HashMap> map = manager.getPlots(); + plots = new ArrayList<>(); + for (Entry> entry : map.entrySet()) { + String areaname = entry.getKey(); + PlotArea pa = PlotSquared.get().getPlotAreaByString(areaname); + if (pa != null) { + for (Entry entry2 : entry.getValue().entrySet()) { + Plot plot = entry2.getValue(); + if (pa.getOwnedPlotAbs(plot.getId()) != null) { + if (pa instanceof SinglePlotArea) { + Plot newPlot = pa.getNextFreePlot(null, plot.getId()); + if (newPlot != null) { + PlotId newId = newPlot.getId(); + PlotId id = plot.getId(); + File worldFile = + new File(PlotSquared.imp().getWorldContainer(), + id.toCommaSeparatedString()); + if (worldFile.exists()) { + File newFile = + new File(PlotSquared.imp().getWorldContainer(), + newId.toCommaSeparatedString()); + worldFile.renameTo(newFile); + } + id.x = newId.x; + id.y = newId.y; + id.recalculateHash(); + plot.setArea(pa); + plots.add(plot); + continue; + } + } + MainUtil.sendMessage(player, + "Skipping duplicate plot: " + plot + " | id=" + plot.temp); + continue; + } + plot.setArea(pa); + plots.add(plot); + } + } else { + HashMap plotmap = PlotSquared.get().plots_tmp + .computeIfAbsent(areaname, k -> new HashMap<>()); + plotmap.putAll(entry.getValue()); + } + } + DBFunc.createPlotsAndData(plots, + () -> MainUtil.sendMessage(player, "&6Database conversion finished!")); + return true; + case "mysql": + if (args.length < 6) { + return MainUtil.sendMessage(player, + "/plot database mysql [host] [port] [username] [password] [database] {prefix}"); + } + String host = args[1]; + String port = args[2]; + String username = args[3]; + String password = args[4]; + String database = args[5]; + if (args.length > 6) { + prefix = args[6]; + } + implementation = new MySQL(host, port, database, username, password); + break; + case "sqlite": + if (args.length < 2) { + return MainUtil.sendMessage(player, "/plot database sqlite [file]"); + } + File sqliteFile = + MainUtil.getFile(PlotSquared.get().IMP.getDirectory(), args[1] + ".db"); + implementation = new SQLite(sqliteFile); + break; + default: + return MainUtil.sendMessage(player, "/plot database [sqlite/mysql]"); + } + try { + SQLManager manager = new SQLManager(implementation, prefix, true); + Database.insertPlots(manager, plots, player); + return true; + } catch (ClassNotFoundException | SQLException e) { + MainUtil.sendMessage(player, "$1Failed to save plots, read stacktrace for info"); + MainUtil.sendMessage(player, + "&d==== Here is an ugly stacktrace, if you are interested in those things ==="); + e.printStackTrace(); + MainUtil.sendMessage(player, "&d==== End of stacktrace ===="); + MainUtil + .sendMessage(player, "$1Please make sure you are using the correct arguments!"); + return false; + } + } catch (ClassNotFoundException | SQLException e) { + MainUtil.sendMessage(player, "$1Failed to open connection, read stacktrace for info"); + MainUtil.sendMessage(player, + "&d==== Here is an ugly stacktrace, if you are interested in those things ==="); + e.printStackTrace(); + MainUtil.sendMessage(player, "&d==== End of stacktrace ===="); + MainUtil.sendMessage(player, "$1Please make sure you are using the correct arguments!"); + return false; + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Debug.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Debug.java new file mode 100644 index 000000000..28594982c --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Debug.java @@ -0,0 +1,46 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.StringMan; + +@CommandDeclaration(command = "debug", category = CommandCategory.DEBUG, + description = "Show debug information", usage = "/plot debug [msg]", permission = "plots.admin") +public class Debug extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + if ((args.length > 0) && args[0].equalsIgnoreCase("msg")) { + StringBuilder msg = new StringBuilder(); + for (C caption : C.values()) { + msg.append(caption.s()).append("\n"); + } + MainUtil.sendMessage(player, msg.toString()); + return true; + } + StringBuilder information = new StringBuilder(); + String header = C.DEBUG_HEADER.s(); + String line = C.DEBUG_LINE.s(); + String section = C.DEBUG_SECTION.s(); + information.append(header); + information.append(getSection(section, "PlotArea")); + information.append( + getLine(line, "Plot Worlds", StringMan.join(PlotSquared.get().getPlotAreas(), ", "))); + information.append(getLine(line, "Owned Plots", PlotSquared.get().getPlots().size())); + information.append(getSection(section, "Messages")); + information.append(getLine(line, "Total Messages", C.values().length)); + information.append(getLine(line, "View all captions", "/plot debug msg")); + MainUtil.sendMessage(player, information.toString()); + return true; + } + + private String getSection(String line, String val) { + return line.replaceAll("%val%", val) + "\n"; + } + + private String getLine(String line, String var, Object val) { + return line.replaceAll("%var%", var).replaceAll("%val%", "" + val) + "\n"; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugAllowUnsafe.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugAllowUnsafe.java new file mode 100644 index 000000000..cbfacfae6 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugAllowUnsafe.java @@ -0,0 +1,30 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@CommandDeclaration(command = "debugallowunsafe", + description = "Allow unsafe actions until toggled off", usage = "/plot debugallowunsafe", + category = CommandCategory.DEBUG, requiredType = RequiredType.NONE, + permission = "plots.debugallowunsafe") public class DebugAllowUnsafe extends SubCommand { + + public static final List unsafeAllowed = new ArrayList<>(); + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + + if (unsafeAllowed.contains(player.getUUID())) { + unsafeAllowed.remove(player.getUUID()); + sendMessage(player, C.DEBUGALLOWUNSAFE_OFF); + } else { + unsafeAllowed.add(player.getUUID()); + sendMessage(player, C.DEBUGALLOWUNSAFE_ON); + } + return true; + } + +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugClaimTest.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugClaimTest.java new file mode 100644 index 000000000..01a344369 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugClaimTest.java @@ -0,0 +1,113 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.ChunkManager; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; +import com.github.intellectualsites.plotsquared.plot.util.WorldUtil; +import com.google.common.collect.BiMap; + +import java.util.ArrayList; +import java.util.Map; +import java.util.UUID; + +@CommandDeclaration(command = "debugclaimtest", description = + "If you accidentally delete your database, this command will attempt to restore all plots based on the data from plot signs. " + + "Execution time may vary", category = CommandCategory.DEBUG, + requiredType = RequiredType.CONSOLE, permission = "plots.debugclaimtest") +public class DebugClaimTest extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + if (args.length < 3) { + return !MainUtil.sendMessage(null, + "If you accidentally delete your database, this command will attempt to restore all plots based on the data from the " + + "plot signs. \n\n&cMissing world arg /plot debugclaimtest {world} {PlotId min} {PlotId max}"); + } + PlotArea area = PlotSquared.get().getPlotAreaByString(args[0]); + if (area == null || !WorldUtil.IMP.isWorld(area.worldname)) { + C.NOT_VALID_PLOT_WORLD.send(player, args[0]); + return false; + } + PlotId min, max; + try { + args[1].split(";"); + args[2].split(";"); + min = PlotId.fromString(args[1]); + max = PlotId.fromString(args[2]); + } catch (Exception ignored) { + return !MainUtil.sendMessage(player, + "&cInvalid min/max values. &7The values are to Plot IDs in the format &cX;Y &7where X;Y are the plot coords\nThe conversion " + + "will only check the plots in the selected area."); + } + MainUtil.sendMessage(player, + "&3Sign Block&8->&3Plot&8: &7Beginning sign to plot conversion. This may take a while..."); + MainUtil.sendMessage(player, + "&3Sign Block&8->&3Plot&8: Found an excess of 250,000 chunks. Limiting search radius... (~3.8 min)"); + PlotManager manager = area.getPlotManager(); + ArrayList plots = new ArrayList<>(); + for (PlotId id : MainUtil.getPlotSelectionIds(min, max)) { + Plot plot = area.getPlotAbs(id); + if (plot.hasOwner()) { + MainUtil.sendMessage(player, " - &cDB Already contains: " + plot.getId()); + continue; + } + Location loc = manager.getSignLoc(area, plot); + ChunkLoc chunk = new ChunkLoc(loc.getX() >> 4, loc.getZ() >> 4); + boolean result = ChunkManager.manager.loadChunk(area.worldname, chunk, false); + if (!result) { + continue; + } + String[] lines = WorldUtil.IMP.getSign(loc); + if (lines != null) { + String line = lines[2]; + if (line != null && line.length() > 2) { + line = line.substring(2); + BiMap map = UUIDHandler.getUuidMap(); + UUID uuid = map.get(new StringWrapper(line)); + if (uuid == null) { + for (Map.Entry stringWrapperUUIDEntry : map + .entrySet()) { + if (stringWrapperUUIDEntry.getKey().value.toLowerCase() + .startsWith(line.toLowerCase())) { + uuid = stringWrapperUUIDEntry.getValue(); + break; + } + } + } + if (uuid == null) { + uuid = UUIDHandler.getUUID(line, null); + } + if (uuid != null) { + MainUtil + .sendMessage(player, " - &aFound plot: " + plot.getId() + " : " + line); + plot.setOwner(uuid); + plots.add(plot); + } else { + MainUtil.sendMessage(player, + " - &cInvalid PlayerName: " + plot.getId() + " : " + line); + } + } + } + } + if (!plots.isEmpty()) { + MainUtil.sendMessage(player, + "&3Sign Block&8->&3Plot&8: &7Updating '" + plots.size() + "' plots!"); + DBFunc.createPlotsAndData(plots, new Runnable() { + @Override public void run() { + MainUtil.sendMessage(player, "&6Database update finished!"); + } + }); + for (Plot plot : plots) { + plot.create(); + } + MainUtil.sendMessage(player, "&3Sign Block&8->&3Plot&8: &7Complete!"); + } else { + MainUtil.sendMessage(player, "No plots were found for the given search."); + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugExec.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugExec.java new file mode 100644 index 000000000..01cd1fefc --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugExec.java @@ -0,0 +1,429 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.flag.Flag; +import com.github.intellectualsites.plotsquared.plot.flag.FlagManager; +import com.github.intellectualsites.plotsquared.plot.generator.HybridUtils; +import com.github.intellectualsites.plotsquared.plot.listener.WEManager; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.*; +import com.github.intellectualsites.plotsquared.plot.util.block.GlobalBlockQueue; +import com.github.intellectualsites.plotsquared.plot.util.expiry.ExpireManager; +import com.github.intellectualsites.plotsquared.plot.util.expiry.PlotAnalysis; +import com.google.common.io.Files; + +import javax.script.*; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.sql.Timestamp; +import java.util.*; + +@CommandDeclaration(command = "debugexec", permission = "plots.admin", + description = "Mutli-purpose debug command", aliases = {"exec", "$"}, + category = CommandCategory.DEBUG) public class DebugExec extends SubCommand { + private ScriptEngine engine; + private Bindings scope; + + public DebugExec() { + try { + if (PlotSquared.get() != null) { + File file = new File(PlotSquared.get().IMP.getDirectory(), + Settings.Paths.SCRIPTS + File.separator + "start.js"); + if (file.exists()) { + init(); + String script = StringMan.join(Files.readLines(new File(new File( + PlotSquared.get().IMP.getDirectory() + File.separator + + Settings.Paths.SCRIPTS), "start.js"), StandardCharsets.UTF_8), + System.getProperty("line.separator")); + this.scope.put("THIS", this); + this.scope.put("PlotPlayer", ConsolePlayer.getConsole()); + this.engine.eval(script, this.scope); + } + } + } catch (IOException | ScriptException ignored) { + ignored.printStackTrace(); + } + } + + public ScriptEngine getEngine() { + if (this.engine == null) { + init(); + } + return this.engine; + } + + public Bindings getScope() { + return this.scope; + } + + public void init() { + if (this.engine != null) { + return; + } + this.engine = new ScriptEngineManager(null).getEngineByName("nashorn"); + if (this.engine == null) { + this.engine = new ScriptEngineManager(null).getEngineByName("JavaScript"); + } + ScriptContext context = new SimpleScriptContext(); + this.scope = context.getBindings(ScriptContext.ENGINE_SCOPE); + + // stuff + this.scope.put("MainUtil", new MainUtil()); + this.scope.put("Settings", new Settings()); + this.scope.put("StringMan", new StringMan()); + this.scope.put("MathMan", new MathMan()); + this.scope.put("FlagManager", new FlagManager()); + + // Classes + this.scope.put("Location", Location.class); + this.scope.put("PlotBlock", PlotBlock.class); + this.scope.put("Plot", Plot.class); + this.scope.put("PlotId", PlotId.class); + this.scope.put("Runnable", Runnable.class); + this.scope.put("RunnableVal", RunnableVal.class); + + // Instances + this.scope.put("PS", PlotSquared.get()); + this.scope.put("GlobalBlockQueue", GlobalBlockQueue.IMP); + this.scope.put("ExpireManager", ExpireManager.IMP); + if (PlotSquared.get().worldedit != null) { + this.scope.put("WEManager", new WEManager()); + } + this.scope.put("TaskManager", TaskManager.IMP); + this.scope.put("TitleManager", AbstractTitle.TITLE_CLASS); + this.scope.put("ConsolePlayer", ConsolePlayer.getConsole()); + this.scope.put("SchematicHandler", SchematicHandler.manager); + this.scope.put("ChunkManager", ChunkManager.manager); + this.scope.put("BlockManager", WorldUtil.IMP); + this.scope.put("SetupUtils", SetupUtils.manager); + this.scope.put("EventUtil", EventUtil.manager); + this.scope.put("EconHandler", EconHandler.manager); + this.scope.put("UUIDHandler", UUIDHandler.implementation); + this.scope.put("DBFunc", DBFunc.dbManager); + this.scope.put("HybridUtils", HybridUtils.manager); + this.scope.put("IMP", PlotSquared.get().IMP); + this.scope.put("MainCommand", MainCommand.getInstance()); + + // enums + for (Enum value : C.values()) { + this.scope.put("C_" + value.name(), value); + } + } + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + List allowed_params = Arrays + .asList("analyze", "calibrate-analysis", "remove-flag", "stop-expire", "start-expire", + "seen", "list-scripts", "start-rgar", "stop-rgar", "help", "addcmd", "runasync", + "run", "allcmd", "all"); + if (args.length > 0) { + String arg = args[0].toLowerCase(); + String script; + boolean async = false; + switch (arg) { + case "analyze": { + Plot plot = player.getCurrentPlot(); + if (plot == null) { + MainUtil.sendMessage(player, C.NOT_IN_PLOT); + return false; + } + PlotAnalysis analysis = plot.getComplexity(null); + if (analysis != null) { + MainUtil.sendMessage(player, "Changes/column: " + analysis.changes / 1.0); + return true; + } + MainUtil.sendMessage(player, "$1Starting task..."); + HybridUtils.manager.analyzePlot(plot, new RunnableVal() { + @Override public void run(PlotAnalysis value) { + 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 ignored) { + 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(() -> MainUtil + .sendMessage(player, "$1Thank you for calibrating plot expiry"), threshold); + return true; + case "stop-expire": + if (ExpireManager.IMP == null || !ExpireManager.IMP.cancelTask()) { + return MainUtil.sendMessage(player, "Task already halted"); + } + return MainUtil.sendMessage(player, "Cancelled task."); + case "remove-flag": + if (args.length != 2) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, + "/plot debugexec remove-flag "); + return false; + } + String flag = args[1]; + for (Plot plot : PlotSquared.get().getBasePlots()) { + Flag flag1 = FlagManager.getFlag(flag); + if (plot.getFlag(flag1).isPresent()) { + plot.removeFlag(flag1); + } + } + return MainUtil.sendMessage(player, "Cleared flag: " + flag); + case "start-rgar": { + if (args.length != 2) { + MainUtil.sendMessage(player, + "&cInvalid syntax: /plot debugexec start-rgar "); + return false; + } + PlotArea area = PlotSquared.get().getPlotAreaByString(args[1]); + if (area == null) { + MainUtil.sendMessage(player, C.NOT_VALID_PLOT_WORLD, args[1]); + return false; + } + boolean result; + if (HybridUtils.regions != null) { + result = + HybridUtils.manager.scheduleRoadUpdate(area, HybridUtils.regions, 0); + } else { + result = HybridUtils.manager.scheduleRoadUpdate(area, 0); + } + if (!result) { + MainUtil.sendMessage(player, + "&cCannot schedule mass schematic update! (Is one already in progress?)"); + return false; + } + return true; + } + case "stop-rgar": + if (!HybridUtils.UPDATE) { + MainUtil.sendMessage(player, "&cTask not running!"); + return false; + } + HybridUtils.UPDATE = false; + MainUtil.sendMessage(player, "&cCancelling task... (Please wait)"); + return true; + case "start-expire": + if (ExpireManager.IMP == null) { + ExpireManager.IMP = new ExpireManager(); + } + if (ExpireManager.IMP.runAutomatedTask()) { + return MainUtil.sendMessage(player, "Started plot expiry task"); + } else { + return MainUtil.sendMessage(player, "Plot expiry task already started"); + } + case "seen": + if (args.length != 2) { + return MainUtil.sendMessage(player, "Use /plot debugexec seen "); + } + UUID uuid = UUIDHandler.getUUID(args[1], null); + if (uuid == null) { + return MainUtil.sendMessage(player, "Player not found: " + args[1]); + } + OfflinePlotPlayer op = UUIDHandler.getUUIDWrapper().getOfflinePlayer(uuid); + if (op == null || op.getLastPlayed() == 0) { + return MainUtil + .sendMessage(player, "Player hasn't connected before: " + args[1]); + } + Timestamp stamp = new Timestamp(op.getLastPlayed()); + Date date = new Date(stamp.getTime()); + MainUtil.sendMessage(player, "PLAYER: " + args[1]); + MainUtil.sendMessage(player, "UUID: " + uuid); + MainUtil.sendMessage(player, "Object: " + date.toGMTString()); + MainUtil.sendMessage(player, "GMT: " + date.toGMTString()); + MainUtil.sendMessage(player, "Local: " + date.toLocaleString()); + return true; + case "h": + case "he": + case "?": + case "help": + MainUtil.sendMessage(player, + "Possible sub commands: /plot debugexec <" + StringMan + .join(allowed_params, "|") + ">"); + return false; + case "addcmd": + try { + final String cmd = StringMan.join(Files.readLines(MainUtil.getFile(new File( + PlotSquared.get().IMP.getDirectory() + File.separator + + Settings.Paths.SCRIPTS), args[1]), StandardCharsets.UTF_8), + System.getProperty("line.separator")); + new Command(MainCommand.getInstance(), true, args[1].split("\\.")[0], null, + RequiredType.NONE, CommandCategory.DEBUG) { + @Override public void execute(PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) { + try { + DebugExec.this.scope.put("PlotPlayer", player); + DebugExec.this.scope.put("args", args); + DebugExec.this.engine.eval(cmd, DebugExec.this.scope); + } catch (ScriptException e) { + e.printStackTrace(); + MainUtil.sendMessage(player, C.COMMAND_WENT_WRONG); + } + } + }; + return true; + } catch (IOException e) { + e.printStackTrace(); + MainUtil + .sendMessage(player, C.COMMAND_SYNTAX, "/plot debugexec addcmd "); + return false; + } + case "runasync": + async = true; + case "run": + try { + script = StringMan.join(Files.readLines(MainUtil.getFile(new File( + PlotSquared.get().IMP.getDirectory() + File.separator + + Settings.Paths.SCRIPTS), args[1]), StandardCharsets.UTF_8), + System.getProperty("line.separator")); + if (args.length > 2) { + HashMap replacements = new HashMap<>(); + for (int i = 2; i < args.length; i++) { + replacements.put("%s" + (i - 2), args[i]); + } + script = StringMan.replaceFromMap(script, replacements); + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + break; + case "list-scripts": + String path = PlotSquared.get().IMP.getDirectory() + File.separator + + Settings.Paths.SCRIPTS; + File folder = new File(path); + File[] filesArray = folder.listFiles(); + + int page; + switch (args.length) { + case 1: + page = 0; + break; + case 2: + if (MathMan.isInteger(args[1])) { + page = Integer.parseInt(args[1]) - 1; + break; + } + default: + C.COMMAND_SYNTAX.send(player, "/plot debugexec list-scripts [#]"); + return false; + } + + List allFiles = Arrays.asList(filesArray); + paginate(player, allFiles, 8, page, + new RunnableVal3() { + + @Override public void run(Integer i, File file, PlotMessage message) { + String name = file.getName(); + message.text("[").color("$3").text(String.valueOf(i)).color("$1") + .text("]").color("$3").text(' ' + name).color("$1"); + } + }, "/plot debugexec list-scripts", "List of scripts"); + return true; + case "allcmd": + if (args.length < 3) { + C.COMMAND_SYNTAX + .send(player, "/plot debugexec allcmd "); + return false; + } + long start = System.currentTimeMillis(); + Command cmd = MainCommand.getInstance().getCommand(args[3]); + String[] params = Arrays.copyOfRange(args, 4, args.length); + if ("true".equals(args[1])) { + Location loc = player.getMeta(PlotPlayer.META_LOCATION); + Plot plot = player.getMeta(PlotPlayer.META_LAST_PLOT); + for (Plot current : PlotSquared.get().getBasePlots()) { + player.setMeta(PlotPlayer.META_LOCATION, current.getBottomAbs()); + player.setMeta(PlotPlayer.META_LAST_PLOT, current); + cmd.execute(player, params, null, null); + } + if (loc == null) { + player.deleteMeta(PlotPlayer.META_LOCATION); + } else { + player.setMeta(PlotPlayer.META_LOCATION, loc); + } + if (plot == null) { + player.deleteMeta(PlotPlayer.META_LAST_PLOT); + } else { + player.setMeta(PlotPlayer.META_LAST_PLOT, plot); + } + player.sendMessage("&c> " + (System.currentTimeMillis() - start)); + return true; + } + init(); + this.scope.put("_2", params); + this.scope.put("_3", cmd); + script = + "_1=PS.getBasePlots().iterator();while(_1.hasNext()){plot=_1.next();if(" + + args[1] + + "){PlotPlayer.setMeta(\"location\",plot.getBottomAbs());PlotPlayer.setMeta(\"lastplot\",plot);_3.onCommand" + + "(PlotPlayer,_2)}}"; + + break; + case "all": + if (args.length < 3) { + C.COMMAND_SYNTAX.send(player, "/plot debugexec all "); + return false; + } + script = + "_1=PS.getBasePlots().iterator();while(_1.hasNext()){plot=_1.next();if(" + + args[1] + "){" + StringMan + .join(Arrays.copyOfRange(args, 2, args.length), " ") + "}}"; + + break; + default: + script = StringMan.join(args, " "); + } + if (!(player instanceof ConsolePlayer)) { + MainUtil.sendMessage(player, C.NOT_CONSOLE); + return false; + } + init(); + this.scope.put("PlotPlayer", player); + PlotSquared.debug("> " + script); + try { + if (async) { + final String toExec = script; + TaskManager.runTaskAsync(() -> { + long start = System.currentTimeMillis(); + Object result = null; + try { + result = DebugExec.this.engine.eval(toExec, DebugExec.this.scope); + } catch (ScriptException e) { + e.printStackTrace(); + } + PlotSquared + .log("> " + (System.currentTimeMillis() - start) + "ms -> " + result); + }); + } else { + long start = System.currentTimeMillis(); + Object result = this.engine.eval(script, this.scope); + PlotSquared + .log("> " + (System.currentTimeMillis() - start) + "ms -> " + result); + } + return true; + } catch (ScriptException e) { + e.printStackTrace(); + return false; + } + } + return false; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugFixFlags.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugFixFlags.java new file mode 100644 index 000000000..da1e27e61 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugFixFlags.java @@ -0,0 +1,52 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Argument; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.flag.Flag; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotArea; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.WorldUtil; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map.Entry; + +@CommandDeclaration(command = "debugfixflags", usage = "/plot debugfixflags ", + permission = "plots.debugfixflags", description = "Attempt to fix all flags for a world", + requiredType = RequiredType.CONSOLE, category = CommandCategory.DEBUG) +public class DebugFixFlags extends SubCommand { + + public DebugFixFlags() { + super(Argument.String); + } + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + PlotArea area = PlotSquared.get().getPlotAreaByString(args[0]); + if (area == null || !WorldUtil.IMP.isWorld(area.worldname)) { + MainUtil.sendMessage(player, C.NOT_VALID_PLOT_WORLD, args[0]); + return false; + } + MainUtil.sendMessage(player, "&8--- &6Starting task &8 ---"); + for (Plot plot : area.getPlots()) { + HashMap, Object> flags = plot.getFlags(); + Iterator, Object>> i = flags.entrySet().iterator(); + boolean changed = false; + while (i.hasNext()) { + if (i.next().getKey() == null) { + changed = true; + i.remove(); + } + } + if (changed) { + DBFunc.setFlags(plot, plot.getFlags()); + } + } + MainUtil.sendMessage(player, "&aDone!"); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugImportWorlds.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugImportWorlds.java new file mode 100644 index 000000000..ddffaf744 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugImportWorlds.java @@ -0,0 +1,58 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.object.PlotId; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal2; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal3; +import com.github.intellectualsites.plotsquared.plot.object.worlds.PlotAreaManager; +import com.github.intellectualsites.plotsquared.plot.object.worlds.SinglePlotArea; +import com.github.intellectualsites.plotsquared.plot.object.worlds.SinglePlotAreaManager; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; +import com.github.intellectualsites.plotsquared.plot.util.WorldUtil; +import com.google.common.base.Charsets; + +import java.io.File; +import java.util.UUID; + +@CommandDeclaration(command = "debugimportworlds", permission = "plots.admin", + description = "Import worlds by player name", requiredType = RequiredType.CONSOLE, + category = CommandCategory.TELEPORT) public class DebugImportWorlds extends Command { + public DebugImportWorlds() { + super(MainCommand.getInstance(), true); + } + + @Override public void execute(PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) throws CommandException { + // UUID.nameUUIDFromBytes(("OfflinePlayer:" + player.getName()).getBytes(Charsets.UTF_8)) + PlotAreaManager pam = PlotSquared.get().getPlotAreaManager(); + if (!(pam instanceof SinglePlotAreaManager)) { + player.sendMessage("Must be a single plot area!"); + return; + } + SinglePlotArea area = ((SinglePlotAreaManager) pam).getArea(); + PlotId id = new PlotId(0, 0); + File container = PlotSquared.imp().getWorldContainer(); + for (File folder : container.listFiles()) { + String name = folder.getName(); + if (!WorldUtil.IMP.isWorld(name) && PlotId.fromString(name) == null) { + UUID uuid = UUIDHandler.getUUID(name, null); + if (uuid == null) { + uuid = + UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)); + } + while (new File(container, id.toCommaSeparatedString()).exists()) { + id = Auto.getNextPlotId(id, 1); + } + File newDir = new File(container, id.toCommaSeparatedString()); + if (folder.renameTo(newDir)) { + area.getPlot(id).setOwner(uuid); + } + } + } + player.sendMessage("Done!"); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugLoadTest.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugLoadTest.java new file mode 100644 index 000000000..f12ce8d1e --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugLoadTest.java @@ -0,0 +1,17 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; + +@CommandDeclaration(command = "debugloadtest", permission = "plots.debugloadtest", + description = "This debug command will force the reload of all plots in the DB", + usage = "/plot debugloadtest", category = CommandCategory.DEBUG, + requiredType = RequiredType.CONSOLE) public class DebugLoadTest extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + PlotSquared.get().plots_tmp = DBFunc.getPlots(); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugPaste.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugPaste.java new file mode 100644 index 000000000..eedba15cf --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugPaste.java @@ -0,0 +1,146 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.IncendoPaster; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.TaskManager; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.NonNull; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +@CommandDeclaration(command = "debugpaste", aliases = "dp", usage = "/plot debugpaste", + description = "Upload settings.yml, worlds.yml, PlotSquared.use_THIS.yml and your latest.log to https://incendo.org", + permission = "plots.debugpaste", category = CommandCategory.DEBUG) public class DebugPaste + extends SubCommand { + + private static String readFile(@NonNull final File file) throws IOException { + final StringBuilder content = new StringBuilder(); + final List lines = new ArrayList<>(); + try (final BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + lines.add(line); + } + } + for (int i = Math.max(0, lines.size() - 1000); i < lines.size(); i++) { + content.append(lines.get(i)).append("\n"); + } + return content.toString(); + } + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + TaskManager.runTaskAsync(() -> { + try { + final IncendoPaster incendoPaster = new IncendoPaster("plotsquared"); + + StringBuilder b = new StringBuilder(); + b.append( + "# Welcome to this paste\n# It is meant to provide us at IntellectualSites with better information about your " + + "problem\n\n"); + b.append("# Server Information\n"); + b.append("server.version: ").append(PlotSquared.get().IMP.getServerImplementation()) + .append("\n"); + b.append("online_mode: ").append(UUIDHandler.getUUIDWrapper()).append(';') + .append(!Settings.UUID.OFFLINE).append('\n'); + b.append("plugins:"); + for (String id : PlotSquared.get().IMP.getPluginIds()) { + String[] split = id.split(":"); + String[] split2 = split[0].split(";"); + String enabled = split.length == 2 ? split[1] : "unknown"; + String name = split2[0]; + String version = split2.length == 2 ? split2[1] : "unknown"; + b.append("\n ").append(name).append(":\n ").append("version: '") + .append(version).append('\'').append("\n enabled: ").append(enabled); + } + b.append("\n\n# YAY! Now, let's see what we can find in your JVM\n"); + Runtime runtime = Runtime.getRuntime(); + b.append("memory.free: ").append(runtime.freeMemory()).append('\n'); + b.append("memory.max: ").append(runtime.maxMemory()).append('\n'); + b.append("java.specification.version: '") + .append(System.getProperty("java.specification.version")).append("'\n"); + b.append("java.vendor: '").append(System.getProperty("java.vendor")).append("'\n"); + b.append("java.version: '").append(System.getProperty("java.version")) + .append("'\n"); + b.append("os.arch: '").append(System.getProperty("os.arch")).append("'\n"); + b.append("os.name: '").append(System.getProperty("os.name")).append("'\n"); + b.append("os.version: '").append(System.getProperty("os.version")).append("'\n\n"); + b.append("# Okay :D Great. You are now ready to create your bug report!"); + b.append( + "\n# You can do so at https://github.com/IntellectualSites/PlotSquared/issues"); + b.append("\n# or via our Discord at https://discord.gg/ngZCzbU"); + + incendoPaster.addFile(new IncendoPaster.PasteFile("information", b.toString())); + + try { + final File logFile = + new File(PlotSquared.get().IMP.getDirectory(), "../../logs/latest.log"); + if (Files.size(logFile.toPath()) > 14_000_000) { + throw new IOException("Too big..."); + } + incendoPaster + .addFile(new IncendoPaster.PasteFile("latest.log", readFile(logFile))); + } catch (IOException ignored) { + MainUtil + .sendMessage(player, "&clatest.log is too big to be pasted, will ignore"); + } + + try { + incendoPaster.addFile(new IncendoPaster.PasteFile("settings.yml", + readFile(PlotSquared.get().configFile))); + } catch (final IllegalArgumentException ignored) { + MainUtil.sendMessage(player, "&cSkipping settings.yml because it's empty"); + } + try { + incendoPaster.addFile(new IncendoPaster.PasteFile("worlds.yml", + readFile(PlotSquared.get().worldsFile))); + } catch (final IllegalArgumentException ignored) { + MainUtil.sendMessage(player, "&cSkipping worlds.yml because it's empty"); + } + try { + incendoPaster.addFile(new IncendoPaster.PasteFile("PlotSquared.use_THIS.yml", + readFile(PlotSquared.get().translationFile))); + } catch (final IllegalArgumentException ignored) { + MainUtil.sendMessage(player, + "&cSkipping PlotSquared.use_THIS.yml because it's empty"); + } + + try { + final String rawResponse = incendoPaster.upload(); + final JsonObject jsonObject = + new JsonParser().parse(rawResponse).getAsJsonObject(); + + if (jsonObject.has("created")) { + final String pasteId = jsonObject.get("paste_id").getAsString(); + final String link = + String.format("https://incendo.org/paste/view/%s", pasteId); + player.sendMessage(C.DEBUG_REPORT_CREATED.s().replace("%url%", link)); + } else { + final String responseMessage = jsonObject.get("response").getAsString(); + MainUtil.sendMessage(player, String + .format("&cFailed to create the debug paste: %s", responseMessage)); + } + } catch (final Throwable throwable) { + throwable.printStackTrace(); + MainUtil.sendMessage(player, + "&cFailed to create the debug paste: " + throwable.getMessage()); + } + } catch (IOException e) { + e.printStackTrace(); + } + }); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugRoadRegen.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugRoadRegen.java new file mode 100644 index 000000000..84489e88b --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugRoadRegen.java @@ -0,0 +1,41 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.generator.HybridPlotManager; +import com.github.intellectualsites.plotsquared.plot.generator.HybridPlotWorld; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotArea; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; + +@CommandDeclaration(command = "debugroadregen", usage = "/plot debugroadregen", + requiredType = RequiredType.NONE, + description = "Regenerate all roads based on the road schematic", + category = CommandCategory.DEBUG, permission = "plots.debugroadregen") +public class DebugRoadRegen extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + Location loc = player.getLocation(); + PlotArea plotArea = loc.getPlotArea(); + if (!(plotArea instanceof HybridPlotWorld)) { + return sendMessage(player, C.NOT_IN_PLOT_WORLD); + } + Plot plot = player.getCurrentPlot(); + if (plot == null) { + C.NOT_IN_PLOT.send(player); + } else if (plot.isMerged()) { + C.REQUIRES_UNMERGED.send(player); + } else { + HybridPlotManager manager = (HybridPlotManager) plotArea.getPlotManager(); + manager.createRoadEast(plotArea, plot); + manager.createRoadSouth(plotArea, plot); + manager.createRoadSouthEast(plotArea, plot); + MainUtil.sendMessage(player, "&6Regenerating plot south/east roads: " + plot.getId() + + "\n&6 - Result: &aSuccess"); + MainUtil.sendMessage(player, "&cTo regenerate all roads: /plot regenallroads"); + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugSaveTest.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugSaveTest.java new file mode 100644 index 000000000..fbbc3267c --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/DebugSaveTest.java @@ -0,0 +1,29 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; + +import java.util.ArrayList; + +@CommandDeclaration(command = "debugsavetest", permission = "plots.debugsavetest", + category = CommandCategory.DEBUG, requiredType = RequiredType.CONSOLE, + usage = "/plot debugsavetest", + description = "This command will force the recreation of all plots in the DB") +public class DebugSaveTest extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + ArrayList plots = new ArrayList(); + plots.addAll(PlotSquared.get().getPlots()); + MainUtil.sendMessage(player, "&6Starting `DEBUGSAVETEST`"); + DBFunc.createPlotsAndData(plots, new Runnable() { + @Override public void run() { + MainUtil.sendMessage(player, "&6Database sync finished!"); + } + }); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Delete.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Delete.java new file mode 100644 index 000000000..b399f5752 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Delete.java @@ -0,0 +1,71 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.*; + + +@CommandDeclaration(command = "delete", permission = "plots.delete", + description = "Delete the plot you stand on", usage = "/plot delete", + aliases = {"dispose", "del"}, category = CommandCategory.CLAIMING, + requiredType = RequiredType.NONE, confirmation = true) public class Delete extends SubCommand { + + // Note: To delete a specific plot use /plot delete + // The syntax also works with any command: /plot + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + Location loc = player.getLocation(); + final Plot plot = loc.getPlotAbs(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + return !sendMessage(player, C.PLOT_UNOWNED); + } + if (!plot.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_DELETE)) { + return !sendMessage(player, C.NO_PLOT_PERMS); + } + final PlotArea plotArea = plot.getArea(); + final java.util.Set plots = plot.getConnectedPlots(); + final int currentPlots = + Settings.Limit.GLOBAL ? player.getPlotCount() : player.getPlotCount(loc.getWorld()); + Runnable run = new Runnable() { + @Override public void run() { + if (plot.getRunning() > 0) { + MainUtil.sendMessage(player, C.WAIT_FOR_TIMER); + return; + } + final long start = System.currentTimeMillis(); + boolean result = plot.deletePlot(new Runnable() { + @Override public void run() { + plot.removeRunning(); + if ((EconHandler.manager != null) && plotArea.USE_ECONOMY) { + Expression valueExr = plotArea.PRICES.get("sell"); + double value = plots.size() * valueExr.evaluate((double) currentPlots); + if (value > 0d) { + EconHandler.manager.depositMoney(player, value); + sendMessage(player, C.ADDED_BALANCE, String.valueOf(value)); + } + } + MainUtil.sendMessage(player, C.DELETING_DONE, + System.currentTimeMillis() - start); + } + }); + if (result) { + plot.addRunning(); + } else { + MainUtil.sendMessage(player, C.WAIT_FOR_TIMER); + } + } + }; + if (hasConfirmation(player)) { + CmdConfirm.addPending(player, getCommandString() + ' ' + plot.getId(), run); + } else { + TaskManager.runTask(run); + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Deny.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Deny.java new file mode 100644 index 000000000..e586f0656 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Deny.java @@ -0,0 +1,114 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Argument; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.*; + +import java.util.Iterator; +import java.util.Set; +import java.util.UUID; + +@CommandDeclaration(command = "deny", aliases = {"d", "ban"}, + description = "Deny a user from a plot", usage = "/plot deny ", + category = CommandCategory.SETTINGS, requiredType = RequiredType.NONE) public class Deny + extends SubCommand { + + public Deny() { + super(Argument.PlayerName); + } + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + + Location location = player.getLocation(); + Plot plot = location.getPlotAbs(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + MainUtil.sendMessage(player, C.PLOT_UNOWNED); + return false; + } + if (!plot.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_DENY)) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return true; + } + Set uuids = MainUtil.getUUIDsFromString(args[0]); + if (uuids.isEmpty()) { + MainUtil.sendMessage(player, C.INVALID_PLAYER, args[0]); + return false; + } + Iterator iter = uuids.iterator(); + while (iter.hasNext()) { + UUID uuid = iter.next(); + if (uuid == DBFunc.EVERYONE && !( + Permissions.hasPermission(player, C.PERMISSION_DENY_EVERYONE) || Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_DENY))) { + MainUtil.sendMessage(player, C.INVALID_PLAYER, MainUtil.getName(uuid)); + continue; + } + if (plot.isOwner(uuid)) { + MainUtil.sendMessage(player, C.ALREADY_OWNER, MainUtil.getName(uuid)); + return false; + } + + if (plot.getDenied().contains(uuid)) { + MainUtil.sendMessage(player, C.ALREADY_ADDED, MainUtil.getName(uuid)); + return false; + } + if (uuid != DBFunc.EVERYONE) { + plot.removeMember(uuid); + plot.removeTrusted(uuid); + } + plot.addDenied(uuid); + EventUtil.manager.callDenied(player, plot, uuid, true); + if (!uuid.equals(DBFunc.EVERYONE)) { + handleKick(UUIDHandler.getPlayer(uuid), plot); + } else { + for (PlotPlayer plotPlayer : plot.getPlayersInPlot()) { + handleKick(plotPlayer, plot); + } + } + } + if (!uuids.isEmpty()) { + MainUtil.sendMessage(player, C.DENIED_ADDED); + } + return true; + } + + private void handleKick(PlotPlayer player, Plot plot) { + if (player == null) { + return; + } + if (!plot.equals(player.getCurrentPlot())) { + return; + } + if (player.hasPermission("plots.admin.entry.denied")) { + return; + } + if (player.getGameMode() == PlotGameMode.SPECTATOR) { + player.stopSpectating(); + } + Location loc = player.getLocation(); + Location spawn = WorldUtil.IMP.getSpawn(loc.getWorld()); + MainUtil.sendMessage(player, C.YOU_GOT_DENIED); + if (plot.equals(spawn.getPlot())) { + Location newSpawn = + WorldUtil.IMP.getSpawn(PlotSquared.get().getPlotAreaManager().getAllWorlds()[0]); + if (plot.equals(newSpawn.getPlot())) { + // Kick from server if you can't be teleported to spawn + player.kick(C.YOU_GOT_DENIED.s()); + } else { + player.teleport(newSpawn); + } + } else { + player.teleport(spawn); + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Desc.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Desc.java new file mode 100644 index 000000000..687eef002 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Desc.java @@ -0,0 +1,30 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.flag.FlagManager; +import com.github.intellectualsites.plotsquared.plot.flag.Flags; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; + +@CommandDeclaration(command = "setdescription", permission = "plots.set.desc", + description = "Set the plot description", usage = "/plot desc ", + aliases = {"desc", "setdesc", "setd", "description"}, category = CommandCategory.SETTINGS, + requiredType = RequiredType.NONE) public class Desc extends SetCommand { + + @Override public boolean set(PlotPlayer player, Plot plot, String desc) { + if (desc.isEmpty()) { + plot.removeFlag(Flags.DESCRIPTION); + MainUtil.sendMessage(player, C.DESC_UNSET); + return true; + } + boolean result = FlagManager.addPlotFlag(plot, Flags.DESCRIPTION, desc); + if (!result) { + MainUtil.sendMessage(player, C.FLAG_NOT_ADDED); + return false; + } + MainUtil.sendMessage(player, C.DESC_SET); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Done.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Done.java new file mode 100644 index 000000000..a37c187cc --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Done.java @@ -0,0 +1,68 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.flag.Flags; +import com.github.intellectualsites.plotsquared.plot.generator.HybridUtils; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; +import com.github.intellectualsites.plotsquared.plot.util.expiry.ExpireManager; +import com.github.intellectualsites.plotsquared.plot.util.expiry.PlotAnalysis; + +@CommandDeclaration(command = "done", aliases = {"submit"}, description = "Mark a plot as done", + permission = "plots.done", category = CommandCategory.SETTINGS, + requiredType = RequiredType.NONE) public class Done extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + Location loc = player.getLocation(); + final Plot plot = loc.getPlotAbs(); + if ((plot == null) || !plot.hasOwner()) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_DONE)) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + if (plot.hasFlag(Flags.DONE)) { + MainUtil.sendMessage(player, C.DONE_ALREADY_DONE); + return false; + } + if (plot.getRunning() > 0) { + MainUtil.sendMessage(player, C.WAIT_FOR_TIMER); + return false; + } + plot.addRunning(); + MainUtil.sendMessage(player, C.GENERATING_LINK); + final Settings.Auto_Clear doneRequirements = Settings.AUTO_CLEAR.get("done"); + if (ExpireManager.IMP == null || doneRequirements == null) { + finish(plot, player, true); + plot.removeRunning(); + } else { + HybridUtils.manager.analyzePlot(plot, new RunnableVal() { + @Override public void run(PlotAnalysis value) { + plot.removeRunning(); + boolean result = + value.getComplexity(doneRequirements) <= doneRequirements.THRESHOLD; + finish(plot, player, result); + } + }); + } + return true; + } + + private void finish(Plot plot, PlotPlayer pp, boolean success) { + if (success) { + long flagValue = System.currentTimeMillis() / 1000; + plot.setFlag(Flags.DONE, flagValue); + MainUtil.sendMessage(pp, C.DONE_SUCCESS); + } else { + MainUtil.sendMessage(pp, C.DONE_INSUFFICIENT_COMPLEXITY); + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Download.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Download.java new file mode 100644 index 000000000..7dd7b7acd --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Download.java @@ -0,0 +1,95 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.flag.Flags; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal; +import com.github.intellectualsites.plotsquared.plot.util.*; +import com.sk89q.jnbt.CompoundTag; + +import java.net.URL; + +@CommandDeclaration(usage = "/plot download [schematic|world]", command = "download", + aliases = {"dl"}, category = CommandCategory.SCHEMATIC, requiredType = RequiredType.NONE, + description = "Download your plot", permission = "plots.download") public class Download + extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + String world = player.getLocation().getWorld(); + if (!PlotSquared.get().hasPlotArea(world)) { + return !sendMessage(player, C.NOT_IN_PLOT_WORLD); + } + final Plot plot = player.getCurrentPlot(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + MainUtil.sendMessage(player, C.PLOT_UNOWNED); + return false; + } + if ((Settings.Done.REQUIRED_FOR_DOWNLOAD && (!plot.getFlag(Flags.DONE).isPresent())) + && !Permissions.hasPermission(player, C.PERMISSION_ADMIN_COMMAND_DOWNLOAD)) { + MainUtil.sendMessage(player, C.DONE_NOT_DONE); + return false; + } + if ((!plot.isOwner(player.getUUID())) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN.s())) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + if (plot.getRunning() > 0) { + MainUtil.sendMessage(player, C.WAIT_FOR_TIMER); + return false; + } + if (args.length == 0 || (args.length == 1 && StringMan + .isEqualIgnoreCaseToAny(args[0], "sch", "schem", "schematic"))) { + if (plot.getVolume() > Integer.MAX_VALUE) { + C.SCHEMATIC_TOO_LARGE.send(player); + return false; + } + plot.addRunning(); + SchematicHandler.manager.getCompoundTag(plot, new RunnableVal() { + @Override public void run(CompoundTag value) { + plot.removeRunning(); + SchematicHandler.manager.upload(value, null, null, new RunnableVal() { + @Override public void run(URL url) { + if (url == null) { + MainUtil.sendMessage(player, C.GENERATING_LINK_FAILED); + return; + } + MainUtil.sendMessage(player, url.toString()); + } + }); + } + }); + } else if (args.length == 1 && StringMan + .isEqualIgnoreCaseToAny(args[0], "mcr", "world", "mca")) { + if (!Permissions.hasPermission(player, C.PERMISSION_DOWNLOAD_WORLD)) { + C.NO_PERMISSION.send(player, C.PERMISSION_DOWNLOAD_WORLD); + return false; + } + MainUtil.sendMessage(player, "&cNote: The `.mca` files are 512x512"); + plot.addRunning(); + WorldUtil.IMP.saveWorld(world); + WorldUtil.IMP.upload(plot, null, null, new RunnableVal() { + @Override public void run(URL url) { + plot.removeRunning(); + if (url == null) { + MainUtil.sendMessage(player, C.GENERATING_LINK_FAILED); + return; + } + MainUtil.sendMessage(player, url.toString()); + } + }); + } else { + C.COMMAND_SYNTAX.send(player, getUsage()); + return false; + } + MainUtil.sendMessage(player, C.GENERATING_LINK); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/FlagCmd.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/FlagCmd.java new file mode 100644 index 000000000..9d0435c07 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/FlagCmd.java @@ -0,0 +1,299 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.flag.*; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotBlock; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.*; + +import java.util.*; + +@CommandDeclaration(command = "setflag", aliases = {"f", "flag", "setf", "setflag"}, + usage = "/plot flag ", description = "Set plot flags", + category = CommandCategory.SETTINGS, requiredType = RequiredType.NONE, + permission = "plots.flag") public class FlagCmd extends SubCommand { + + private boolean checkPermValue(PlotPlayer player, Flag flag, String key, String value) { + key = key.toLowerCase(); + value = value.toLowerCase(); + String perm = C.PERMISSION_SET_FLAG_KEY_VALUE.f(key.toLowerCase(), value.toLowerCase()); + if (flag instanceof IntegerFlag && MathMan.isInteger(value)) { + try { + int numeric = Integer.parseInt(value); + perm = perm.substring(0, perm.length() - value.length() - 1); + if (numeric > 0) { + int checkRange = PlotSquared.get().getPlatform().equalsIgnoreCase("bukkit") ? + numeric : + Settings.Limit.MAX_PLOTS; + final boolean result = player.hasPermissionRange(perm, checkRange) >= numeric; + if (!result) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_SET_FLAG_KEY_VALUE + .f(key.toLowerCase(), value.toLowerCase())); + } + return result; + } + + } catch (NumberFormatException ignore) { + } + } else if (flag instanceof PlotBlockListFlag) { + final PlotBlockListFlag blockListFlag = (PlotBlockListFlag) flag; + final HashSet parsedBlocks = blockListFlag.parseValue(value); + for (final PlotBlock block : parsedBlocks) { + final String permission = C.PERMISSION_SET_FLAG_KEY_VALUE + .f(key.toLowerCase(), block.getRawId().toString().toLowerCase()); + final boolean result = Permissions.hasPermission(player, permission); + if (!result) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_SET_FLAG_KEY_VALUE.f(key.toLowerCase(), value.toLowerCase())); + return false; + } + } + return true; + } + final boolean result = Permissions.hasPermission(player, perm); + if (!result) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_SET_FLAG_KEY_VALUE.f(key.toLowerCase(), value.toLowerCase())); + } + return result; + } + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + + /* + * plot flag set fly true + * plot flag remove fly + * plot flag remove use 1,3 + * plot flag add use 2,4 + * plot flag list + */ + if (args.length == 0) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot flag "); + return false; + } + Location loc = player.getLocation(); + Plot plot = loc.getPlotAbs(); + if (plot == null) { + MainUtil.sendMessage(player, C.NOT_IN_PLOT); + return false; + } + if (!plot.hasOwner()) { + sendMessage(player, C.PLOT_NOT_CLAIMED); + return false; + } + if (!plot.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_SET_FLAG_OTHER)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_SET_FLAG_OTHER); + return false; + } + Flag flag = null; + if (args.length > 1) { + flag = FlagManager.getFlag(args[1]); + if (flag == null || flag.isReserved()) { + boolean suggested = false; + try { + StringComparison> stringComparison = + new StringComparison<>(args[1], Flags.getFlags()); + String best = stringComparison.getBestMatch(); + if (best != null) { + MainUtil.sendMessage(player, C.NOT_VALID_FLAG_SUGGESTED, best); + suggested = true; + } + } catch (final Exception ignored) { /* Happens sometimes because of mean code */ } + if (!suggested) { + MainUtil.sendMessage(player, C.NOT_VALID_FLAG); + } + return false; + } + } + switch (args[0].toLowerCase()) { + case "info": { + if (!Permissions.hasPermission(player, C.PERMISSION_SET_FLAG)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, "plots.flag.info"); + return false; + } + if (args.length != 2) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot flag info "); + return false; + } + // flag key + MainUtil.sendMessage(player, C.FLAG_KEY, flag.getName()); + // flag type + MainUtil.sendMessage(player, C.FLAG_TYPE, flag.getClass().getSimpleName()); + // Flag type description + MainUtil.sendMessage(player, C.FLAG_DESC, flag.getValueDescription()); + return true; + } + case "set": { + if (!Permissions.hasPermission(player, C.PERMISSION_SET_FLAG)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_SET_FLAG); + return false; + } + if (args.length < 3) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot flag set "); + return false; + } + String value = StringMan.join(Arrays.copyOfRange(args, 2, args.length), " "); + if (!checkPermValue(player, flag, args[1], value)) { + return false; + } + Object parsed = flag.parseValue(value); + if (parsed == null) { + MainUtil.sendMessage(player, "&c" + flag.getValueDescription()); + return false; + } + boolean result = plot.setFlag(flag, parsed); + if (!result) { + MainUtil.sendMessage(player, C.FLAG_NOT_ADDED); + return false; + } + MainUtil.sendMessage(player, C.FLAG_ADDED); + return true; + } + case "remove": { + if (!Permissions.hasPermission(player, C.PERMISSION_FLAG_REMOVE)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_FLAG_REMOVE); + return false; + } + if (args.length != 2 && args.length != 3) { + MainUtil + .sendMessage(player, C.COMMAND_SYNTAX, "/plot flag remove [values]"); + return false; + } + if (!Permissions + .hasPermission(player, C.PERMISSION_SET_FLAG_KEY.f(args[1].toLowerCase()))) { + if (args.length != 3) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_SET_FLAG_KEY.f(args[1].toLowerCase())); + return false; + } + for (String entry : args[2].split(",")) { + if (!checkPermValue(player, flag, args[1], entry)) { + return false; + } + } + } + if (args.length == 3 && flag instanceof ListFlag) { + String value = StringMan.join(Arrays.copyOfRange(args, 2, args.length), " "); + Optional flag1 = + plot.getFlag((Flag>) flag); + if (flag1.isPresent()) { + boolean o = flag1.get().removeAll((Collection) flag.parseValue(value)); + if (o) { + if (flag1.get().isEmpty()) { + final boolean result = plot.removeFlag(flag); + if (result) { + MainUtil.sendMessage(player, C.FLAG_REMOVED); + } else { + MainUtil.sendMessage(player, C.FLAG_NOT_REMOVED); + } + return true; + } else { + MainUtil.sendMessage(player, C.FLAG_REMOVED); + } + } else { + MainUtil.sendMessage(player, C.FLAG_NOT_REMOVED); + return false; + } + } + DBFunc.setFlags(plot, plot.getFlags()); + return true; + } else { + boolean result = plot.removeFlag(flag); + if (!result) { + MainUtil.sendMessage(player, C.FLAG_NOT_REMOVED); + return false; + } + } + if (flag == Flags.TIME) { + player.setTime(Long.MAX_VALUE); + } else if (flag == Flags.WEATHER) { + player.setWeather(PlotWeather.RESET); + } + MainUtil.sendMessage(player, C.FLAG_REMOVED); + return true; + } + case "add": + if (!Permissions.hasPermission(player, C.PERMISSION_FLAG_ADD)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_FLAG_ADD); + return false; + } + if (args.length < 3) { + MainUtil + .sendMessage(player, C.COMMAND_SYNTAX, "/plot flag add "); + return false; + } + for (String entry : args[2].split(",")) { + if (!checkPermValue(player, flag, args[1], entry)) { + return false; + } + } + String value = StringMan.join(Arrays.copyOfRange(args, 2, args.length), " "); + Object parsed = flag.parseValue(value); + if (parsed == null) { + MainUtil.sendMessage(player, "&c" + flag.getValueDescription()); + return false; + } + Object val = parsed; + if (flag instanceof ListFlag) { + Optional flag1 = + plot.getFlag((Flag>) flag); + if (flag1.isPresent()) { + boolean o = flag1.get().addAll((Collection) parsed); + if (o) { + MainUtil.sendMessage(player, C.FLAG_ADDED); + val = flag1.get(); + } else { + MainUtil.sendMessage(player, C.FLAG_NOT_ADDED); + return false; + } + } + } + boolean result = plot.setFlag(flag, val); + if (!result) { + MainUtil.sendMessage(player, C.FLAG_NOT_ADDED); + return false; + } + MainUtil.sendMessage(player, C.FLAG_ADDED); + return true; + case "list": + if (!Permissions.hasPermission(player, C.PERMISSION_FLAG_LIST)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_FLAG_LIST); + return false; + } + if (args.length > 1) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot flag list"); + return false; + } + HashMap> flags = new HashMap<>(); + for (Flag flag1 : Flags.getFlags()) { + String type = flag1.getClass().getSimpleName(); + if (!flags.containsKey(type)) { + flags.put(type, new ArrayList<>()); + } + flags.get(type).add(flag1.getName()); + } + StringBuilder message = new StringBuilder(); + String prefix = ""; + for (Map.Entry> entry : flags.entrySet()) { + String category = entry.getKey(); + List flagNames = entry.getValue(); + Collections.sort(flagNames); + message.append(prefix).append("&6").append(category).append(": &7") + .append(StringMan.join(flagNames, ", ")); + prefix = "\n"; + } + MainUtil.sendMessage(player, message.toString()); + return true; + } + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot flag "); + return false; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/GenerateDocs.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/GenerateDocs.java new file mode 100644 index 000000000..5a69479b5 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/GenerateDocs.java @@ -0,0 +1,221 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.util.StringMan; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GenerateDocs { + + public static void main(String[] args) { + new WE_Anywhere(); + List commands = MainCommand.getInstance().getCommands(); + GenerateDocs.log("### Want to document some commands?"); + GenerateDocs.log(" - This page is automatically generated"); + GenerateDocs + .log(" - Fork the project and add a javadoc comment to one of the command classes"); + GenerateDocs.log(" - Then do a pull request and it will be added to this page"); + GenerateDocs.log(""); + GenerateDocs.log("# Contents"); + for (CommandCategory category : CommandCategory.values()) { + GenerateDocs.log("###### " + category.name()); + for (Command command : MainCommand.getInstance().getCommands(category, null)) { + GenerateDocs.log(" - [/plot " + command.getId() + + "](https://github.com/IntellectualSites/PlotSquared/wiki/Commands#" + command + .getId() + ") "); + } + GenerateDocs.log(""); + } + GenerateDocs.log("# Commands"); + for (Command command : commands) { + GenerateDocs.printCommand(command); + } + } + + public static void printCommand(Command command) { + try { + String clazz = command.getClass().getSimpleName(); + String name = command.getId(); + + // Header + String source = + "https://github.com/IntellectualSites/PlotSquared/tree/master/Core/src/main/java/com/intellectualcrafters/plot/commands/" + + clazz + ".java"; + GenerateDocs.log("## [" + name.toUpperCase() + "](" + source + ") "); + + File file = new File( + "Core/src/main/java/com/intellectualcrafters/plot/commands/" + clazz + ".java"); + List lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); + List perms = GenerateDocs.getPerms(name, lines); + List usages = GenerateDocs.getUsage(name, lines); + String comment = GenerateDocs.getComments(lines); + + GenerateDocs.log("#### Description"); + GenerateDocs.log('`' + command.getDescription() + '`'); + if (!comment.isEmpty()) { + GenerateDocs.log("##### Comments"); + GenerateDocs.log("``` java"); + GenerateDocs.log(comment); + GenerateDocs.log("```"); + } + + GenerateDocs.log("#### Usage "); + String mainUsage = command.getUsage().replaceAll("\\{label\\}", "plot"); + if (!usages.isEmpty() && !usages.get(0).equalsIgnoreCase(mainUsage)) { + GenerateDocs.log("##### Primary "); + GenerateDocs.log(" - `" + mainUsage + "` "); + GenerateDocs.log(""); + GenerateDocs.log("##### Other "); + GenerateDocs.log(" - `" + StringMan.join(usages, "`\n - `") + "` "); + GenerateDocs.log(""); + } else { + GenerateDocs.log('`' + mainUsage + "` "); + } + + if (command.getRequiredType() != RequiredType.NONE) { + GenerateDocs.log("#### Required callers"); + GenerateDocs.log('`' + command.getRequiredType().name() + '`'); + } + + List aliases = command.getAliases(); + if (!aliases.isEmpty()) { + GenerateDocs.log("#### Aliases"); + GenerateDocs.log('`' + StringMan.getString(command.getAliases()) + '`'); + } + + GenerateDocs.log("#### Permissions"); + if (!perms.isEmpty()) { + GenerateDocs.log("##### Primary"); + GenerateDocs.log(" - `" + command.getPermission() + "` "); + GenerateDocs.log(""); + GenerateDocs.log("##### Other"); + GenerateDocs.log(" - `" + StringMan.join(perms, "`\n - `") + '`'); + GenerateDocs.log(""); + } else { + GenerateDocs.log('`' + command.getPermission() + "` "); + } + GenerateDocs.log("***"); + GenerateDocs.log(""); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static List getUsage(String cmd, List lines) { + Pattern p = Pattern.compile("\"([^\"]*)\""); + HashSet usages = new HashSet<>(); + for (String line : lines) { + if (line.contains("COMMAND_SYNTAX") && !line.contains("getUsage()")) { + Matcher m = p.matcher(line); + String prefix = ""; + StringBuilder usage = new StringBuilder(); + while (m.find()) { + String match = m.group(1); + usage.append(prefix).append(match); + prefix = " "; + } + if (usage.length() != 0) { + usages.add(usage.toString().trim().replaceAll(" ", " ") + .replaceAll("\\{label\\}", "plot")); + } + } + } + return new ArrayList<>(usages); + } + + public static List getPerms(String cmd, List lines) { + HashSet perms = new HashSet<>(); + Pattern p = Pattern.compile("\"([^\"]*)\""); + Pattern p2 = Pattern.compile("C.PERMISSION_\\s*(\\w+)"); + String last = null; + for (String line : lines) { + + Matcher m2 = p2.matcher(line); + while (m2.find()) { + perms.add(C.valueOf("PERMISSION_" + m2.group(1)).s()); + } + if (line.contains("Permissions.hasPermission(")) { + String[] split = line.split("Permissions.hasPermission"); + split = Arrays.copyOfRange(split, 1, split.length); + for (String method : split) { + String perm = method.split("[,|)]")[1].trim(); + if (!perm.equalsIgnoreCase(perm)) { + if (perm.startsWith("C.")) { + perm = C.valueOf(perm.split("\\.")[1]).s(); + } else { + continue; + } + } else { + perm = perm.substring(1, perm.length() - 1); + } + perms.add(perm); + } + Matcher m = p.matcher(line); + while (m.find()) { + String perm = m.group(1); + if (perm.endsWith(".")) { + perm += ""; + } + if (perm.startsWith(".")) { + perms.remove(last); + perms.add(last + perm); + } else if (perm.contains(".")) { + last = perm; + perms.add(perm); + } + } + } else if (line.contains("Permissions.hasPermissionRange")) { + String[] split = line.split("Permissions.hasPermissionRange"); + split = Arrays.copyOfRange(split, 1, split.length); + for (String method : split) { + String perm = method.split("[,|)]")[1].trim(); + if (!perm.equalsIgnoreCase(perm)) { + if (perm.startsWith("C.")) { + perm = C.valueOf(perm.split("\\.")[1]).s(); + } else { + continue; + } + } else { + perm = perm.substring(1, perm.length() - 1); + } + perms.add(perm + ".<#>"); + } + } + } + switch (cmd.toLowerCase()) { + case "auto": + case "claim": + perms.add("plots.plot.<#>"); + break; + } + return new ArrayList<>(perms); + } + + public static String getComments(List lines) { + StringBuilder result = new StringBuilder(); + for (String line : lines) { + line = line.trim(); + if (line.startsWith("/** ") || line.startsWith("*/ ") || line.startsWith("* ")) { + line = + line.replaceAll("/[*][*] ", "").replaceAll("[*]/ ", "").replaceAll("[*] ", "") + .trim(); + result.append(line + '\n'); + } + } + return result.toString().trim(); + } + + public static void log(String s) { + System.out.println(s); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Grant.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Grant.java new file mode 100644 index 000000000..d29bd76bb --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Grant.java @@ -0,0 +1,71 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal2; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal3; +import com.github.intellectualsites.plotsquared.plot.util.ByteArrayUtilities; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; + +import java.util.UUID; + +@CommandDeclaration(command = "grant", category = CommandCategory.CLAIMING, + usage = "/plot grant [player]", permission = "plots.grant", + requiredType = RequiredType.NONE) public class Grant extends Command { + + public Grant() { + super(MainCommand.getInstance(), true); + } + + @Override public void execute(final PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) throws CommandException { + checkTrue(args.length >= 1 && args.length <= 2, C.COMMAND_SYNTAX, getUsage()); + final String arg0 = args[0].toLowerCase(); + switch (arg0) { + case "add": + case "check": + if (!Permissions.hasPermission(player, C.PERMISSION_GRANT.f(arg0))) { + C.NO_PERMISSION.send(player, C.PERMISSION_GRANT.f(arg0)); + return; + } + if (args.length > 2) { + break; + } + final UUID uuid = + args.length == 2 ? UUIDHandler.getUUIDFromString(args[1]) : player.getUUID(); + if (uuid == null) { + C.INVALID_PLAYER.send(player, args[1]); + return; + } + MainUtil.getPersistentMeta(uuid, "grantedPlots", new RunnableVal() { + @Override public void run(byte[] array) { + if (arg0.equals("check")) { // check + int granted = + array == null ? 0 : ByteArrayUtilities.bytesToInteger(array); + C.GRANTED_PLOTS.send(player, granted); + } else { // add + int amount = + 1 + (array == null ? 0 : ByteArrayUtilities.bytesToInteger(array)); + boolean replace = array != null; + String key = "grantedPlots"; + byte[] rawData = ByteArrayUtilities.integerToBytes(amount); + PlotPlayer online = UUIDHandler.getPlayer(uuid); + if (online != null) { + online.setPersistentMeta(key, rawData); + } else { + DBFunc.addPersistentMeta(uuid, key, rawData, replace); + } + } + } + }); + } + C.COMMAND_SYNTAX.send(player, getUsage()); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Help.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Help.java new file mode 100644 index 000000000..ff697953c --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Help.java @@ -0,0 +1,91 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal2; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal3; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.MathMan; +import com.github.intellectualsites.plotsquared.plot.util.StringMan; +import com.github.intellectualsites.plotsquared.plot.util.helpmenu.HelpMenu; + +@CommandDeclaration(command = "help", description = "Get this help menu", aliases = {"he", "?"}, + category = CommandCategory.INFO, usage = "help [category|#]", permission = "plots.use") +public class Help extends Command { + public Help(Command parent) { + super(parent, true); + } + + @Override public boolean canExecute(PlotPlayer player, boolean message) { + return true; + } + + @Override public void execute(PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) { + switch (args.length) { + case 0: + displayHelp(player, null, 0); + return; + case 1: + if (MathMan.isInteger(args[0])) { + try { + displayHelp(player, null, Integer.parseInt(args[0])); + } catch (NumberFormatException ignored) { + displayHelp(player, null, 1); + } + } else { + displayHelp(player, args[0], 1); + } + return; + case 2: + if (MathMan.isInteger(args[1])) { + try { + displayHelp(player, args[0], Integer.parseInt(args[1])); + } catch (NumberFormatException ignored) { + displayHelp(player, args[0], 1); + } + } + return; + default: + C.COMMAND_SYNTAX.send(player, getUsage()); + } + } + + public void displayHelp(PlotPlayer player, String cat, int page) { + CommandCategory catEnum = null; + if (cat != null) { + if (!StringMan.isEqualIgnoreCase(cat, "all")) { + for (CommandCategory c : CommandCategory.values()) { + if (StringMan.isEqualIgnoreCaseToAny(cat, c.name(), c.toString())) { + catEnum = c; + cat = c.name(); + break; + } + } + if (catEnum == null) { + cat = null; + } + } + } + if (cat == null && page == 0) { + StringBuilder builder = new StringBuilder(); + builder.append(C.HELP_HEADER.s()); + for (CommandCategory c : CommandCategory.values()) { + builder.append("\n" + StringMan + .replaceAll(C.HELP_INFO_ITEM.s(), "%category%", c.toString().toLowerCase(), + "%category_desc%", c.toString())); + } + builder.append("\n").append(C.HELP_INFO_ITEM.s().replaceAll("%category%", "all") + .replaceAll("%category_desc%", "Display all commands")); + builder.append("\n" + C.HELP_FOOTER.s()); + MainUtil.sendMessage(player, builder.toString(), false); + return; + } + page--; + new HelpMenu(player).setCategory(catEnum).getCommands().generateMaxPages() + .generatePage(page, getParent().toString()).render(); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Inbox.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Inbox.java new file mode 100644 index 000000000..908e35afc --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Inbox.java @@ -0,0 +1,198 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal; +import com.github.intellectualsites.plotsquared.plot.object.comment.CommentInbox; +import com.github.intellectualsites.plotsquared.plot.object.comment.PlotComment; +import com.github.intellectualsites.plotsquared.plot.util.CommentManager; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.StringMan; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@CommandDeclaration(command = "inbox", description = "Review the comments for a plot", + usage = "/plot inbox [inbox] [delete |clear|page]", permission = "plots.inbox", + category = CommandCategory.CHAT, requiredType = RequiredType.NONE) public class Inbox + extends SubCommand { + + public void displayComments(PlotPlayer player, List oldComments, int page) { + if (oldComments == null || oldComments.isEmpty()) { + MainUtil.sendMessage(player, C.INBOX_EMPTY); + return; + } + PlotComment[] comments = oldComments.toArray(new PlotComment[oldComments.size()]); + if (page < 0) { + page = 0; + } + // Get the total pages + // int totalPages = ((int) Math.ceil(12 * + int totalPages = (int) Math.ceil(comments.length / 12); + if (page > totalPages) { + page = totalPages; + } + // Only display 12 per page + int max = page * 12 + 12; + if (max > comments.length) { + max = comments.length; + } + StringBuilder string = new StringBuilder(); + string.append(StringMan + .replaceAll(C.COMMENT_LIST_HEADER_PAGED.s(), "%amount%", comments.length, "%cur", + page + 1, "%max", totalPages + 1, "%word", "all") + '\n'); + + // This might work xD + for (int x = page * 12; x < max; x++) { + PlotComment comment = comments[x]; + String color; + if (player.getName().equals(comment.senderName)) { + color = "&a"; + } else { + color = "&7"; + } + string.append("&8[&7#").append(x + 1).append("&8][&7").append(comment.world).append(';') + .append(comment.id).append("&8][&6").append(comment.senderName).append("&8]") + .append(color).append(comment.comment).append('\n'); + } + MainUtil.sendMessage(player, string.toString()); + } + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + final Plot plot = player.getCurrentPlot(); + if (plot == null) { + sendMessage(player, C.NOT_IN_PLOT); + return false; + } + if (!plot.hasOwner()) { + sendMessage(player, C.PLOT_UNOWNED); + return false; + } + if (args.length == 0) { + sendMessage(player, C.COMMAND_SYNTAX, + "/plot inbox [inbox] [delete |clear|page]"); + for (final CommentInbox inbox : CommentManager.inboxes.values()) { + if (inbox.canRead(plot, player)) { + if (!inbox.getComments(plot, new RunnableVal>() { + @Override public void run(List value) { + if (value != null) { + int total = 0; + int unread = 0; + for (PlotComment comment : value) { + total++; + if (comment.timestamp > CommentManager + .getTimestamp(player, inbox.toString())) { + unread++; + } + } + if (total != 0) { + String color; + if (unread > 0) { + color = "&c"; + } else { + color = ""; + } + sendMessage(player, C.INBOX_ITEM, + color + inbox.toString() + " (" + total + '/' + unread + + ')'); + return; + } + } + sendMessage(player, C.INBOX_ITEM, inbox.toString()); + } + })) { + sendMessage(player, C.INBOX_ITEM, inbox.toString()); + } + } + } + return false; + } + final CommentInbox inbox = CommentManager.inboxes.get(args[0].toLowerCase()); + if (inbox == null) { + sendMessage(player, C.INVALID_INBOX, + StringMan.join(CommentManager.inboxes.keySet(), ", ")); + return false; + } + player.setMeta("inbox:" + inbox.toString(), System.currentTimeMillis()); + final int page; + if (args.length > 1) { + switch (args[1].toLowerCase()) { + case "delete": + if (!inbox.canModify(plot, player)) { + sendMessage(player, C.NO_PERM_INBOX_MODIFY); + return false; + } + if (args.length != 3) { + sendMessage(player, C.COMMAND_SYNTAX, + "/plot inbox " + inbox.toString() + " delete "); + } + final int index; + try { + index = Integer.parseInt(args[2]); + if (index < 1) { + sendMessage(player, C.NOT_VALID_INBOX_INDEX, index + ""); + return false; + } + } catch (NumberFormatException ignored) { + sendMessage(player, C.COMMAND_SYNTAX, + "/plot inbox " + inbox.toString() + " delete "); + return false; + } + + if (!inbox.getComments(plot, new RunnableVal>() { + @Override public void run(List value) { + if (index > value.size()) { + sendMessage(player, C.NOT_VALID_INBOX_INDEX, index + ""); + return; + } + PlotComment comment = value.get(index - 1); + inbox.removeComment(plot, comment); + plot.getSettings().removeComment(comment); + MainUtil.sendMessage(player, C.COMMENT_REMOVED, comment.comment); + } + })) { + sendMessage(player, C.NOT_IN_PLOT); + return false; + } + return true; + case "clear": + if (!inbox.canModify(plot, player)) { + sendMessage(player, C.NO_PERM_INBOX_MODIFY); + } + inbox.clearInbox(plot); + Optional> comments = + plot.getSettings().getComments(inbox.toString()); + comments + .ifPresent(plotComments -> plot.getSettings().removeComments(plotComments)); + MainUtil.sendMessage(player, C.COMMENT_REMOVED, "*"); + return true; + default: + try { + page = Integer.parseInt(args[1]); + } catch (NumberFormatException ignored) { + sendMessage(player, C.COMMAND_SYNTAX, + "/plot inbox [inbox] [delete |clear|page]"); + return false; + } + } + } else { + page = 1; + } + if (!inbox.canRead(plot, player)) { + sendMessage(player, C.NO_PERM_INBOX); + return false; + } + if (!inbox.getComments(plot, new RunnableVal>() { + @Override public void run(List value) { + displayComments(player, value, page); + } + })) { + sendMessage(player, C.PLOT_UNOWNED); + return false; + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Info.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Info.java new file mode 100644 index 000000000..6241e1d51 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Info.java @@ -0,0 +1,155 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.expiry.ExpireManager; + +import java.util.UUID; + +@CommandDeclaration(command = "info", aliases = "i", description = "Display plot info", + usage = "/plot info ", category = CommandCategory.INFO) public class Info + extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + Plot plot; + String arg; + if (args.length > 0) { + arg = args[0]; + switch (arg) { + case "trusted": + case "alias": + case "inv": + case "biome": + case "denied": + case "flags": + case "id": + case "size": + case "members": + case "seen": + case "owner": + case "rating": + plot = MainUtil.getPlotFromString(player, null, false); + break; + default: + plot = MainUtil.getPlotFromString(player, arg, false); + if (args.length == 2) { + arg = args[1]; + } else { + arg = null; + } + break; + } + if (plot == null) { + plot = player.getCurrentPlot(); + } + } else { + arg = null; + plot = player.getCurrentPlot(); + } + if (plot == null) { + MainUtil.sendMessage(player, C.NOT_IN_PLOT.s()); + return false; + } + if (arg != null) { + if (args.length == 1) { + args = new String[0]; + } else { + args = new String[] {args[1]}; + } + } + if (args.length == 1 && args[0].equalsIgnoreCase("inv")) { + PlotInventory inv = new PlotInventory(player) { + @Override public boolean onClick(int index) { + // TODO InfoInventory not implemented yet!!!!!!!! + // See plot rating or musicsubcommand on examples + return false; + } + }; + UUID uuid = player.getUUID(); + String name = MainUtil.getName(plot.owner); + inv.setItem(1, new PlotItemStack(388, (short) 0, 1, "&cPlot Info", + "&cID: &6" + plot.getId().toString(), "&cOwner: &6" + name, + "&cAlias: &6" + plot.getAlias(), + "&cBiome: &6" + plot.getBiome().replaceAll("_", "").toLowerCase(), + "&cCan Build: &6" + plot.isAdded(uuid), + "&cSeen: &6" + MainUtil.secToTime((int) (ExpireManager.IMP.getAge(plot) / 1000)), + "&cIs Denied: &6" + plot.isDenied(uuid))); + inv.setItem(1, new PlotItemStack(388, (short) 0, 1, "&cTrusted", + "&cAmount: &6" + plot.getTrusted().size(), + "&8Click to view a list of the trusted users")); + inv.setItem(1, new PlotItemStack(388, (short) 0, 1, "&cMembers", + "&cAmount: &6" + plot.getMembers().size(), + "&8Click to view a list of plot members")); + inv.setItem(1, new PlotItemStack(388, (short) 0, 1, "&cDenied", "&cDenied", + "&cAmount: &6" + plot.getDenied().size(), + "&8Click to view a list of denied players")); + inv.setItem(1, new PlotItemStack(388, (short) 0, 1, "&cFlags", "&cFlags", + "&cAmount: &6" + plot.getFlags().size(), "&8Click to view a list of plot flags")); + inv.openInventory(); + return true; + } + boolean hasOwner = plot.hasOwner(); + // Wildcard player {added} + boolean containsEveryone = plot.getTrusted().contains(DBFunc.EVERYONE); + boolean trustedEveryone = plot.getMembers().contains(DBFunc.EVERYONE); + // Unclaimed? + if (!hasOwner && !containsEveryone && !trustedEveryone) { + MainUtil + .sendMessage(player, C.PLOT_INFO_UNCLAIMED, plot.getId().x + ";" + plot.getId().y); + return true; + } + String info = C.PLOT_INFO.s(); + boolean full; + if (arg != null) { + info = getCaption(arg); + if (info == null) { + MainUtil.sendMessage(player, + "&6Categories&7: &amembers&7, &aalias&7, &abiome&7, &aseen&7, &adenied&7, &aflags&7, &aid&7, &asize&7, &atrusted&7, " + + "&aowner&7, &arating"); + return false; + } + full = true; + } else { + full = false; + } + MainUtil.format(info, plot, player, full, new RunnableVal() { + @Override public void run(String value) { + MainUtil.sendMessage(player, + C.PLOT_INFO_HEADER.s() + '\n' + value + '\n' + C.PLOT_INFO_FOOTER.s(), false); + } + }); + return true; + } + + private String getCaption(String string) { + switch (string) { + case "trusted": + return C.PLOT_INFO_TRUSTED.s(); + case "alias": + return C.PLOT_INFO_ALIAS.s(); + case "biome": + return C.PLOT_INFO_BIOME.s(); + case "denied": + return C.PLOT_INFO_DENIED.s(); + case "flags": + return C.PLOT_INFO_FLAGS.s(); + case "id": + return C.PLOT_INFO_ID.s(); + case "size": + return C.PLOT_INFO_SIZE.s(); + case "members": + return C.PLOT_INFO_MEMBERS.s(); + case "owner": + return C.PLOT_INFO_OWNER.s(); + case "rating": + return C.PLOT_INFO_RATING.s(); + case "seen": + return C.PLOT_INFO_SEEN.s(); + default: + return null; + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Kick.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Kick.java new file mode 100644 index 000000000..fc16db715 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Kick.java @@ -0,0 +1,92 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Argument; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; +import com.github.intellectualsites.plotsquared.plot.util.WorldUtil; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@CommandDeclaration(command = "kick", aliases = {"k"}, description = "Kick a player from your plot", + permission = "plots.kick", usage = "/plot kick ", category = CommandCategory.TELEPORT, + requiredType = RequiredType.NONE) public class Kick extends SubCommand { + + public Kick() { + super(Argument.PlayerName); + } + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + Location location = player.getLocation(); + Plot plot = location.getPlot(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if ((!plot.hasOwner() || !plot.isOwner(player.getUUID())) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_KICK)) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + Set uuids = MainUtil.getUUIDsFromString(args[0]); + if (uuids.isEmpty()) { + MainUtil.sendMessage(player, C.INVALID_PLAYER, args[0]); + return false; + } + Set players = new HashSet<>(); + for (UUID uuid : uuids) { + if (uuid == DBFunc.EVERYONE) { + for (PlotPlayer pp : plot.getPlayersInPlot()) { + if (pp == player || Permissions + .hasPermission(pp, C.PERMISSION_ADMIN_ENTRY_DENIED)) { + continue; + } + players.add(pp); + } + continue; + } + PlotPlayer pp = UUIDHandler.getPlayer(uuid); + if (pp != null) { + players.add(pp); + } + } + players.remove(player); // Don't ever kick the calling player + if (players.isEmpty()) { + MainUtil.sendMessage(player, C.INVALID_PLAYER, args[0]); + return false; + } + for (PlotPlayer player2 : players) { + if (!plot.equals(player2.getCurrentPlot())) { + MainUtil.sendMessage(player, C.INVALID_PLAYER, args[0]); + return false; + } + if (Permissions.hasPermission(player2, C.PERMISSION_ADMIN_ENTRY_DENIED)) { + C.CANNOT_KICK_PLAYER.send(player, player2.getName()); + return false; + } + Location spawn = WorldUtil.IMP.getSpawn(location.getWorld()); + C.YOU_GOT_KICKED.send(player2); + if (plot.equals(spawn.getPlot())) { + Location newSpawn = WorldUtil.IMP + .getSpawn(PlotSquared.get().getPlotAreaManager().getAllWorlds()[0]); + if (plot.equals(newSpawn.getPlot())) { + // Kick from server if you can't be teleported to spawn + player2.kick(C.YOU_GOT_KICKED.s()); + } else { + player2.plotkick(newSpawn); + } + } else { + player2.plotkick(spawn); + } + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Leave.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Leave.java new file mode 100644 index 000000000..5504ce777 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Leave.java @@ -0,0 +1,48 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal2; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal3; +import com.github.intellectualsites.plotsquared.plot.util.EventUtil; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; + +import java.util.UUID; + +@CommandDeclaration(command = "leave", + description = "Removes self from being trusted or a member of the plot", + permission = "plots.leave", category = CommandCategory.CLAIMING, + requiredType = RequiredType.NONE) public class Leave extends Command { + public Leave() { + super(MainCommand.getInstance(), true); + } + + @Override public void execute(PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) throws CommandException { + final Plot plot = check(player.getCurrentPlot(), C.NOT_IN_PLOT); + checkTrue(plot.hasOwner(), C.PLOT_UNOWNED); + checkTrue(plot.isAdded(player.getUUID()), C.NO_PLOT_PERMS); + checkTrue(args.length == 0, C.COMMAND_SYNTAX, getUsage()); + if (plot.isOwner(player.getUUID())) { + checkTrue(plot.hasOwner(), C.ALREADY_OWNER); + // TODO setowner, other + } else { + UUID uuid = player.getUUID(); + if (plot.isAdded(uuid)) { + if (plot.removeTrusted(uuid)) { + EventUtil.manager.callTrusted(player, plot, uuid, false); + } + if (plot.removeMember(uuid)) { + EventUtil.manager.callMember(player, plot, uuid, false); + } + MainUtil.sendMessage(player, C.INVALID_PLAYER, args[0]); + } else { + MainUtil.sendMessage(player, C.REMOVED_PLAYERS, 1); + } + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/ListCmd.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/ListCmd.java new file mode 100644 index 000000000..9df74d30b --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/ListCmd.java @@ -0,0 +1,397 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.PlotSquared.SortType; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.flag.Flags; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.*; +import com.github.intellectualsites.plotsquared.plot.util.expiry.ExpireManager; + +import java.util.*; +import java.util.Map.Entry; + +@CommandDeclaration(command = "list", aliases = {"l", "find", "search"}, description = "List plots", + permission = "plots.list", category = CommandCategory.INFO, + usage = "/plot list > [#]") +public class ListCmd extends SubCommand { + + private String[] getArgumentList(PlotPlayer player) { + List args = new ArrayList<>(); + if (EconHandler.manager != null && Permissions + .hasPermission(player, C.PERMISSION_LIST_FORSALE)) { + args.add("forsale"); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_MINE)) { + args.add("mine"); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_SHARED)) { + args.add("shared"); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_WORLD)) { + args.add("world"); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_TOP)) { + args.add("top"); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_ALL)) { + args.add("all"); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_UNOWNED)) { + args.add("unowned"); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_UNKNOWN)) { + args.add("unknown"); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_PLAYER)) { + args.add(""); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_WORLD)) { + args.add(""); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_DONE)) { + args.add("done"); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_EXPIRED)) { + args.add("expired"); + } + if (Permissions.hasPermission(player, C.PERMISSION_LIST_FUZZY)) { + args.add("fuzzy "); + } + return args.toArray(new String[args.size()]); + } + + public void noArgs(PlotPlayer player) { + MainUtil.sendMessage(player, + C.SUBCOMMAND_SET_OPTIONS_HEADER.s() + Arrays.toString(getArgumentList(player))); + } + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + if (args.length < 1) { + noArgs(player); + return false; + } + int page = 0; + if (args.length > 1) { + try { + page = Integer.parseInt(args[args.length - 1]); + --page; + if (page < 0) { + page = 0; + } + } catch (NumberFormatException ignored) { + page = -1; + } + } + + List plots = null; + + String world = player.getLocation().getWorld(); + PlotArea area = player.getApplicablePlotArea(); + String arg = args[0].toLowerCase(); + boolean sort = true; + switch (arg) { + case "mine": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_MINE)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_MINE); + return false; + } + sort = false; + plots = PlotSquared.get().sortPlotsByTemp(PlotSquared.get().getBasePlots(player)); + break; + case "shared": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_SHARED)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_SHARED); + return false; + } + plots = new ArrayList<>(); + for (Plot plot : PlotSquared.get().getPlots()) { + if (plot.getTrusted().contains(player.getUUID()) || plot.getMembers() + .contains(player.getUUID())) { + plots.add(plot); + } + } + break; + case "world": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_WORLD)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_WORLD); + return false; + } + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_WORLD_NAME.f(world))) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_LIST_WORLD_NAME.f(world)); + return false; + } + plots = new ArrayList<>(PlotSquared.get().getPlots(world)); + break; + case "expired": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_EXPIRED)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_EXPIRED); + return false; + } + plots = ExpireManager.IMP == null ? + new ArrayList() : + new ArrayList<>(ExpireManager.IMP.getPendingExpired()); + break; + case "area": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_AREA)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_AREA); + return false; + } + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_WORLD_NAME.f(world))) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_LIST_WORLD_NAME.f(world)); + return false; + } + plots = area == null ? new ArrayList() : new ArrayList<>(area.getPlots()); + break; + case "all": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_ALL)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_ALL); + return false; + } + plots = new ArrayList<>(PlotSquared.get().getPlots()); + break; + case "done": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_DONE)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_DONE); + return false; + } + plots = new ArrayList<>(); + for (Plot plot : PlotSquared.get().getPlots()) { + Optional flag = plot.getFlag(Flags.DONE); + if (flag.isPresent()) { + plots.add(plot); + } + } + Collections.sort(plots, new Comparator() { + @Override public int compare(Plot a, Plot b) { + String va = "" + a.getFlags().get(Flags.DONE); + String vb = "" + b.getFlags().get(Flags.DONE); + if (MathMan.isInteger(va)) { + if (MathMan.isInteger(vb)) { + return Integer.parseInt(vb) - Integer.parseInt(va); + } + return -1; + } + return 1; + } + }); + sort = false; + break; + case "top": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_TOP)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_TOP); + return false; + } + plots = new ArrayList<>(PlotSquared.get().getPlots()); + Collections.sort(plots, new Comparator() { + @Override public int compare(Plot p1, Plot p2) { + double v1 = 0; + int p1s = p1.getSettings().getRatings().size(); + int p2s = p2.getRatings().size(); + if (!p1.getSettings().getRatings().isEmpty()) { + for (Entry entry : p1.getRatings().entrySet()) { + double av = entry.getValue().getAverageRating(); + v1 += av * av; + } + v1 /= p1s; + v1 += p1s; + } + double v2 = 0; + if (!p2.getSettings().getRatings().isEmpty()) { + for (Entry entry : p2.getRatings().entrySet()) { + double av = entry.getValue().getAverageRating(); + v2 += av * av; + } + v2 /= p2s; + v2 += p2s; + } + if (v2 == v1 && v2 != 0) { + return p2s - p1s; + } + return (int) Math.signum(v2 - v1); + } + }); + sort = false; + break; + case "forsale": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_FORSALE)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_FORSALE); + return false; + } + if (EconHandler.manager == null) { + break; + } + plots = new ArrayList<>(); + for (Plot plot : PlotSquared.get().getPlots()) { + Optional price = plot.getFlag(Flags.PRICE); + if (price.isPresent()) { + plots.add(plot); + } + } + break; + case "unowned": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_UNOWNED)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_UNOWNED); + return false; + } + plots = new ArrayList<>(); + for (Plot plot : PlotSquared.get().getPlots()) { + if (plot.owner == null) { + plots.add(plot); + } + } + break; + case "unknown": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_UNKNOWN)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_UNKNOWN); + return false; + } + plots = new ArrayList<>(); + for (Plot plot : PlotSquared.get().getPlots()) { + if (plot.owner == null) { + continue; + } + if (UUIDHandler.getName(plot.owner) == null) { + plots.add(plot); + } + } + break; + case "fuzzy": + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_FUZZY)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_FUZZY); + return false; + } + if (args.length < (page == -1 ? 2 : 3)) { + C.COMMAND_SYNTAX.send(player, "/plot list fuzzy [#]"); + return false; + } + String term; + if (MathMan.isInteger(args[args.length - 1])) { + term = StringMan.join(Arrays.copyOfRange(args, 1, args.length - 1), " "); + } else { + term = StringMan.join(Arrays.copyOfRange(args, 1, args.length), " "); + } + plots = MainUtil.getPlotsBySearch(term); + sort = false; + break; + default: + if (PlotSquared.get().hasPlotArea(args[0])) { + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_WORLD)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_WORLD); + return false; + } + if (!Permissions + .hasPermission(player, C.PERMISSION_LIST_WORLD_NAME.f(args[0]))) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_LIST_WORLD_NAME.f(args[0])); + return false; + } + plots = new ArrayList<>(PlotSquared.get().getPlots(args[0])); + break; + } + UUID uuid = UUIDHandler.getUUID(args[0], null); + if (uuid == null) { + try { + uuid = UUID.fromString(args[0]); + } catch (Exception ignored) { + } + } + if (uuid != null) { + if (!Permissions.hasPermission(player, C.PERMISSION_LIST_PLAYER)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_LIST_PLAYER); + return false; + } + sort = false; + plots = PlotSquared.get().sortPlotsByTemp(PlotSquared.get().getPlots(uuid)); + break; + } + } + + if (plots == null) { + sendMessage(player, C.DID_YOU_MEAN, + new StringComparison<>(args[0], new String[] {"mine", "shared", "world", "all"}) + .getBestMatch()); + return false; + } + + if (plots.isEmpty()) { + MainUtil.sendMessage(player, C.FOUND_NO_PLOTS); + return false; + } + displayPlots(player, plots, 12, page, area, args, sort); + return true; + } + + public void displayPlots(final PlotPlayer player, List plots, int pageSize, int page, + PlotArea area, String[] args, boolean sort) { + // Header + Iterator iterator = plots.iterator(); + while (iterator.hasNext()) { + if (!iterator.next().isBasePlot()) { + iterator.remove(); + } + } + if (sort) { + plots = PlotSquared.get().sortPlots(plots, SortType.CREATION_DATE, area); + } + this.paginate(player, plots, pageSize, page, + new RunnableVal3() { + @Override public void run(Integer i, Plot plot, PlotMessage message) { + String color; + if (plot.owner == null) { + color = "$3"; + } else if (plot.isOwner(player.getUUID())) { + color = "$1"; + } else if (plot.isAdded(player.getUUID())) { + color = "$4"; + } else if (plot.isDenied(player.getUUID())) { + color = "$2"; + } else { + color = "$1"; + } + PlotMessage trusted = new PlotMessage().text(C.color(C.PLOT_INFO_TRUSTED.s() + .replaceAll("%trusted%", MainUtil.getPlayerList(plot.getTrusted())))) + .color("$1"); + PlotMessage members = new PlotMessage().text(C.color(C.PLOT_INFO_MEMBERS.s() + .replaceAll("%members%", MainUtil.getPlayerList(plot.getMembers())))) + .color("$1"); + String strFlags = StringMan.join(plot.getFlags().values(), ","); + if (strFlags.isEmpty()) { + strFlags = C.NONE.s(); + } + PlotMessage flags = new PlotMessage() + .text(C.color(C.PLOT_INFO_FLAGS.s().replaceAll("%flags%", strFlags))) + .color("$1"); + message.text("[").color("$3").text(i + "") + .command("/plot visit " + plot.getArea() + ";" + plot.getId()) + .tooltip("/plot visit " + plot.getArea() + ";" + plot.getId()).color("$1") + .text("]").color("$3").text(" " + plot.toString()) + .tooltip(trusted, members, flags) + .command("/plot info " + plot.getArea() + ";" + plot.getId()).color(color) + .text(" - ").color("$2"); + String prefix = ""; + for (UUID uuid : plot.getOwners()) { + String name = UUIDHandler.getName(uuid); + if (name == null) { + message = message.text(prefix).color("$4").text("unknown").color("$2") + .tooltip(uuid.toString()).suggest(uuid.toString()); + } else { + PlotPlayer pp = UUIDHandler.getPlayer(uuid); + if (pp != null) { + message = message.text(prefix).color("$4").text(name).color("$1") + .tooltip(new PlotMessage("Online").color("$4")); + } else { + message = message.text(prefix).color("$4").text(name).color("$1") + .tooltip(new PlotMessage("Offline").color("$3")); + } + } + prefix = ", "; + } + } + }, "/plot list " + args[0], C.PLOT_LIST_HEADER_PAGED.s()); + } + +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Load.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Load.java new file mode 100644 index 000000000..cc9106d28 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Load.java @@ -0,0 +1,179 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.object.schematic.Schematic; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; +import com.github.intellectualsites.plotsquared.plot.util.SchematicHandler; +import com.github.intellectualsites.plotsquared.plot.util.TaskManager; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; + +@CommandDeclaration(command = "load", aliases = {"restore"}, category = CommandCategory.SCHEMATIC, + requiredType = RequiredType.NONE, description = "Load your plot", permission = "plots.load", + usage = "/plot load") public class Load extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + String world = player.getLocation().getWorld(); + if (!PlotSquared.get().hasPlotArea(world)) { + return !sendMessage(player, C.NOT_IN_PLOT_WORLD); + } + final Plot plot = player.getCurrentPlot(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + MainUtil.sendMessage(player, C.PLOT_UNOWNED); + return false; + } + if (!plot.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_LOAD)) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + if (plot.getRunning() > 0) { + MainUtil.sendMessage(player, C.WAIT_FOR_TIMER); + return false; + } + + if (args.length != 0) { + if (args.length == 1) { + List schematics = player.getMeta("plot_schematics"); + if (schematics == null) { + // No schematics found: + MainUtil.sendMessage(player, C.LOAD_NULL); + return false; + } + String schem; + try { + schem = schematics.get(Integer.parseInt(args[0]) - 1); + } catch (Exception ignored) { + // use /plot load + MainUtil + .sendMessage(player, C.NOT_VALID_NUMBER, "(1, " + schematics.size() + ')'); + return false; + } + final URL url; + try { + url = new URL(Settings.Web.URL + "saves/" + player.getUUID() + '/' + schem); + } catch (MalformedURLException e) { + e.printStackTrace(); + MainUtil.sendMessage(player, C.LOAD_FAILED); + return false; + } + plot.addRunning(); + MainUtil.sendMessage(player, C.GENERATING_COMPONENT); + TaskManager.runTaskAsync(() -> { + Schematic schematic = SchematicHandler.manager.getSchematic(url); + if (schematic == null) { + plot.removeRunning(); + sendMessage(player, C.SCHEMATIC_INVALID, + "non-existent or not in gzip format"); + return; + } + PlotArea area = plot.getArea(); + SchematicHandler.manager + .paste(schematic, plot, 0, area.MIN_BUILD_HEIGHT, 0, false, + new RunnableVal() { + @Override public void run(Boolean value) { + plot.removeRunning(); + if (value) { + sendMessage(player, C.SCHEMATIC_PASTE_SUCCESS); + } else { + sendMessage(player, C.SCHEMATIC_PASTE_FAILED); + } + } + }); + }); + return true; + } + plot.removeRunning(); + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot load "); + return false; + } + + // list schematics + + List schematics = player.getMeta("plot_schematics"); + if (schematics == null) { + plot.addRunning(); + TaskManager.runTaskAsync(() -> { + List schematics1 = SchematicHandler.manager.getSaves(player.getUUID()); + plot.removeRunning(); + if ((schematics1 == null) || schematics1.isEmpty()) { + MainUtil.sendMessage(player, C.LOAD_FAILED); + return; + } + player.setMeta("plot_schematics", schematics1); + displaySaves(player); + }); + } else { + displaySaves(player); + } + return true; + } + + public void displaySaves(PlotPlayer player) { + List schematics = player.getMeta("plot_schematics"); + for (int i = 0; i < Math.min(schematics.size(), 32); i++) { + try { + String schematic = schematics.get(i).split("\\.")[0]; + String[] split = schematic.split("_"); + if (split.length < 5) { + continue; + } + String time = + secToTime((System.currentTimeMillis() / 1000) - Long.parseLong(split[0])); + String world = split[1]; + PlotId id = PlotId.fromString(split[2] + ';' + split[3]); + String size = split[4]; + String color = "$4"; + MainUtil.sendMessage(player, + "$3[$2" + (i + 1) + "$3] " + color + time + "$3 | " + color + world + ';' + id + + "$3 | " + color + size + 'x' + size); + } catch (Exception e) { + e.printStackTrace(); + } + } + MainUtil.sendMessage(player, C.LOAD_LIST); + } + + public String secToTime(long time) { + StringBuilder toreturn = new StringBuilder(); + if (time >= 33868800) { + int years = (int) (time / 33868800); + time -= years * 33868800; + toreturn.append(years).append("y "); + } + if (time >= 604800) { + int weeks = (int) (time / 604800); + time -= weeks * 604800; + toreturn.append(weeks).append("w "); + } + if (time >= 86400) { + int days = (int) (time / 86400); + time -= days * 86400; + toreturn.append(days).append("d "); + } + if (time >= 3600) { + int hours = (int) (time / 3600); + time -= hours * 3600; + toreturn.append(hours).append("h "); + } + if (time >= 60) { + int minutes = (int) (time / 60); + time -= minutes * 60; + toreturn.append(minutes).append("m "); + } + if (toreturn.length() == 0 || (time > 0)) { + toreturn.append(time).append("s "); + } + return toreturn.toString().trim(); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/MainCommand.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/MainCommand.java new file mode 100644 index 000000000..0e1630e76 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/MainCommand.java @@ -0,0 +1,286 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.CmdConfirm; +import com.github.intellectualsites.plotsquared.plot.util.EconHandler; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; + +import java.util.Arrays; + +/** + * PlotSquared command class. + */ +@CommandDeclaration(command = "plot", + aliases = {"plots", "p", "plotsquared", "plot2", "p2", "ps", "2", "plotme", "plotz", "ap"}) +public class MainCommand extends Command { + + private static MainCommand instance; + public Help help; + public Toggle toggle; + + private MainCommand() { + super(null, true); + instance = this; + } + + public static MainCommand getInstance() { + if (instance == null) { + instance = new MainCommand(); + new Buy(); + new Save(); + new Load(); + new Confirm(); + new Template(); + new Download(); + new Template(); + new Setup(); + new Area(); + new DebugSaveTest(); + new DebugLoadTest(); + new CreateRoadSchematic(); + new DebugAllowUnsafe(); + new RegenAllRoads(); + new Claim(); + new Auto(); + new Visit(); + new Set(); + new Clear(); + new Delete(); + new Trust(); + new Add(); + new Leave(); + new Deny(); + new Remove(); + new Info(); + new Near(); + new ListCmd(); + new Debug(); + new SchematicCmd(); + new PluginCmd(); + new Purge(); + new Reload(); + new Relight(); + new Merge(); + new DebugPaste(); + new Unlink(); + new Kick(); + new Rate(); + new DebugClaimTest(); + new Inbox(); + new Comment(); + new Database(); + new Swap(); + new Music(); + new DebugRoadRegen(); + new Trust(); + new DebugExec(); + new FlagCmd(); + new Target(); + new DebugFixFlags(); + new Move(); + new Condense(); + new Copy(); + new Chat(); + new Trim(); + new Done(); + new Continue(); + new Middle(); + new Grant(); + // Set commands + new Owner(); + new Desc(); + new Biome(); + new Alias(); + new SetHome(); + new Cluster(); + new DebugImportWorlds(); + // Referenced commands + instance.toggle = new Toggle(); + instance.help = new Help(instance); + } + return instance; + } + + public static boolean onCommand(final PlotPlayer player, String... args) { + if (args.length >= 1 && args[0].contains(":")) { + String[] split2 = args[0].split(":"); + if (split2.length == 2) { + // Ref: c:v, this will push value to the last spot in the array + // ex. /p h:2 SomeUsername + // > /p h SomeUsername 2 + String[] tmp = new String[args.length + 1]; + tmp[0] = split2[0]; + tmp[args.length] = split2[1]; + if (args.length >= 2) { + System.arraycopy(args, 1, tmp, 1, args.length - 1); + } + args = tmp; + } + } + try { + getInstance().execute(player, args, new RunnableVal3() { + @Override + public void run(final Command cmd, final Runnable success, final Runnable failure) { + if (cmd.hasConfirmation(player)) { + CmdConfirm.addPending(player, cmd.getUsage(), new Runnable() { + @Override public void run() { + if (EconHandler.manager != null) { + PlotArea area = player.getApplicablePlotArea(); + if (area != null) { + Expression priceEval = + area.PRICES.get(cmd.getFullId()); + Double price = + priceEval != null ? priceEval.evaluate(0d) : 0d; + if (price != null + && EconHandler.manager.getMoney(player) < price) { + if (failure != null) { + failure.run(); + } + return; + } + } + } + if (success != null) { + success.run(); + } + } + }); + return; + } + if (EconHandler.manager != null) { + PlotArea area = player.getApplicablePlotArea(); + if (area != null) { + Expression priceEval = area.PRICES.get(cmd.getFullId()); + Double price = priceEval != null ? priceEval.evaluate(0d) : 0d; + if (price != 0d && EconHandler.manager.getMoney(player) < price) { + if (failure != null) { + failure.run(); + } + return; + } + } + } + if (success != null) { + success.run(); + } + } + }, new RunnableVal2() { + @Override public void run(Command cmd, CommandResult result) { + // Post command stuff!? + } + }); + } catch (CommandException e) { + e.perform(player); + } + // Always true + return true; + } + + @Deprecated + /** + * @Deprecated legacy + */ public void addCommand(SubCommand command) { + PlotSquared.debug("Command registration is now done during instantiation"); + } + + @Override public void execute(final PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) { + // Clear perm caching // + player.deleteMeta("perm"); + // Optional command scope // + Location loc = null; + Plot plot = null; + boolean tp = false; + if (args.length >= 2) { + PlotArea area = player.getApplicablePlotArea(); + Plot newPlot = Plot.fromString(area, args[0]); + if (newPlot != null && (player instanceof ConsolePlayer || newPlot.getArea() + .equals(area) || Permissions.hasPermission(player, C.PERMISSION_ADMIN)) && !newPlot + .isDenied(player.getUUID())) { + Location newLoc = newPlot.getCenter(); + if (player.canTeleport(newLoc)) { + // Save meta + loc = player.getMeta(PlotPlayer.META_LOCATION); + plot = player.getMeta(PlotPlayer.META_LAST_PLOT); + tp = true; + // Set loc + player.setMeta(PlotPlayer.META_LOCATION, newLoc); + player.setMeta(PlotPlayer.META_LAST_PLOT, newPlot); + } else { + C.BORDER.send(player); + } + // Trim command + args = Arrays.copyOfRange(args, 1, args.length); + } + if (args.length >= 2 && !args[0].isEmpty() && args[0].charAt(0) == '-') { + switch (args[0].substring(1)) { + case "f": + confirm = new RunnableVal3() { + @Override + public void run(Command cmd, Runnable success, Runnable failure) { + if (EconHandler.manager != null) { + PlotArea area = player.getApplicablePlotArea(); + if (area != null) { + Expression priceEval = + area.PRICES.get(cmd.getFullId()); + Double price = + priceEval != null ? priceEval.evaluate(0d) : 0d; + if (price != 0d + && EconHandler.manager.getMoney(player) < price) { + if (failure != null) { + failure.run(); + } + return; + } + } + } + if (success != null) { + success.run(); + } + } + }; + args = Arrays.copyOfRange(args, 1, args.length); + break; + default: + C.INVALID_COMMAND_FLAG.send(player); + return; + } + } + } + try { + super.execute(player, args, confirm, whenDone); + } catch (CommandException e) { + throw e; + } catch (Throwable e) { + e.printStackTrace(); + String message = e.getLocalizedMessage(); + if (message != null) { + C.ERROR.send(player, message); + } else { + C.ERROR.send(player); + } + } + // Reset command scope // + if (tp && !(player instanceof ConsolePlayer)) { + if (loc == null) { + player.deleteMeta(PlotPlayer.META_LOCATION); + } else { + player.setMeta(PlotPlayer.META_LOCATION, loc); + } + if (plot == null) { + player.deleteMeta(PlotPlayer.META_LAST_PLOT); + } else { + player.setMeta(PlotPlayer.META_LAST_PLOT, plot); + } + } + } + + @Override public boolean canExecute(PlotPlayer player, boolean message) { + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Merge.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Merge.java new file mode 100644 index 000000000..94152175e --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Merge.java @@ -0,0 +1,198 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.*; + +import java.util.UUID; + +@CommandDeclaration(command = "merge", aliases = "m", + description = "Merge the plot you are standing on, with another plot", + permission = "plots.merge", usage = "/plot merge [removeroads]", + category = CommandCategory.SETTINGS, requiredType = RequiredType.NONE, confirmation = true) +public class Merge extends SubCommand { + + public static final String[] values = new String[] {"north", "east", "south", "west", "auto"}; + public static final String[] aliases = new String[] {"n", "e", "s", "w", "all"}; + + public static String direction(float yaw) { + yaw = yaw / 90; + int i = Math.round(yaw); + switch (i) { + case -4: + case 0: + case 4: + return "SOUTH"; + case -1: + case 3: + return "EAST"; + case -2: + case 2: + return "NORTH"; + case -3: + case 1: + return "WEST"; + default: + return ""; + } + } + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + Location loc = player.getLocationFull(); + final Plot plot = loc.getPlotAbs(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + MainUtil.sendMessage(player, C.PLOT_UNOWNED); + return false; + } + UUID uuid = player.getUUID(); + if (!plot.isOwner(uuid)) { + if (!Permissions.hasPermission(player, C.PERMISSION_ADMIN_COMMAND_MERGE)) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } else { + uuid = plot.guessOwner(); + } + } + final PlotArea plotArea = plot.getArea(); + Expression priceExr = + plotArea.PRICES.getOrDefault("merge", Expression.constant(0d)); + final int size = plot.getConnectedPlots().size(); + final double price = priceExr.evaluate((double) size); + if (EconHandler.manager != null && plotArea.USE_ECONOMY && price > 0d + && EconHandler.manager.getMoney(player) < price) { + sendMessage(player, C.CANNOT_AFFORD_MERGE, String.valueOf(price)); + return false; + } + final int maxSize = + Permissions.hasPermissionRange(player, "plots.merge", Settings.Limit.MAX_PLOTS); + if (size - 1 > maxSize) { + MainUtil.sendMessage(player, C.NO_PERMISSION, "plots.merge." + (size + 1)); + return false; + } + int direction = -1; + if (args.length == 0) { + switch (direction(player.getLocationFull().getYaw())) { + case "NORTH": + direction = 0; + break; + case "EAST": + direction = 1; + break; + case "SOUTH": + direction = 2; + break; + case "WEST": + direction = 3; + break; + } + } else { + if ("all".equalsIgnoreCase(args[0]) || "auto".equalsIgnoreCase(args[0])) { + boolean terrain = true; + if (args.length == 2) { + terrain = "true".equalsIgnoreCase(args[1]); + } + if (!terrain && !Permissions.hasPermission(player, C.PERMISSION_MERGE_KEEPROAD)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_MERGE_KEEPROAD.s()); + return true; + } + if (plot.autoMerge(-1, maxSize, uuid, terrain)) { + if (EconHandler.manager != null && plotArea.USE_ECONOMY && price > 0d) { + EconHandler.manager.withdrawMoney(player, price); + sendMessage(player, C.REMOVED_BALANCE, String.valueOf(price)); + } + MainUtil.sendMessage(player, C.SUCCESS_MERGE); + return true; + } + MainUtil.sendMessage(player, C.NO_AVAILABLE_AUTOMERGE); + return false; + + } + for (int i = 0; i < values.length; i++) { + if (args[0].equalsIgnoreCase(values[i]) || args[0].equalsIgnoreCase(aliases[i])) { + direction = i; + break; + } + } + } + if (direction == -1) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, + "/plot merge <" + StringMan.join(values, "|") + "> [removeroads]"); + MainUtil + .sendMessage(player, C.DIRECTION.s().replaceAll("%dir%", direction(loc.getYaw()))); + return false; + } + final boolean terrain; + if (args.length == 2) { + terrain = "true".equalsIgnoreCase(args[1]); + } else { + terrain = true; + } + if (!terrain && !Permissions.hasPermission(player, C.PERMISSION_MERGE_KEEPROAD)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_MERGE_KEEPROAD.s()); + return true; + } + if (plot.autoMerge(direction, maxSize - size, uuid, terrain)) { + if (EconHandler.manager != null && plotArea.USE_ECONOMY && price > 0d) { + EconHandler.manager.withdrawMoney(player, price); + sendMessage(player, C.REMOVED_BALANCE, String.valueOf(price)); + } + MainUtil.sendMessage(player, C.SUCCESS_MERGE); + return true; + } + Plot adjacent = plot.getRelative(direction); + if (adjacent == null || !adjacent.hasOwner() || adjacent.getMerged((direction + 2) % 4) + || adjacent.isOwner(uuid)) { + MainUtil.sendMessage(player, C.NO_AVAILABLE_AUTOMERGE); + return false; + } + if (!Permissions.hasPermission(player, C.PERMISSION_MERGE_OTHER)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_MERGE_OTHER); + return false; + } + java.util.Set uuids = adjacent.getOwners(); + boolean isOnline = false; + for (final UUID owner : uuids) { + final PlotPlayer accepter = UUIDHandler.getPlayer(owner); + if (accepter == null) { + continue; + } + isOnline = true; + final int dir = direction; + Runnable run = () -> { + MainUtil.sendMessage(accepter, C.MERGE_ACCEPTED); + plot.autoMerge(dir, maxSize - size, owner, terrain); + PlotPlayer plotPlayer = UUIDHandler.getPlayer(player.getUUID()); + if (plotPlayer == null) { + sendMessage(accepter, C.MERGE_NOT_VALID); + return; + } + if (EconHandler.manager != null && plotArea.USE_ECONOMY && price > 0d) { + if (EconHandler.manager.getMoney(player) < price) { + sendMessage(player, C.CANNOT_AFFORD_MERGE, String.valueOf(price)); + return; + } + EconHandler.manager.withdrawMoney(player, price); + sendMessage(player, C.REMOVED_BALANCE, String.valueOf(price)); + } + MainUtil.sendMessage(player, C.SUCCESS_MERGE); + }; + if (hasConfirmation(player)) { + CmdConfirm.addPending(accepter, + C.MERGE_REQUEST_CONFIRM.s().replaceAll("%s", player.getName()), run); + } else { + run.run(); + } + } + if (!isOnline) { + MainUtil.sendMessage(player, C.NO_AVAILABLE_AUTOMERGE); + return false; + } + MainUtil.sendMessage(player, C.MERGE_REQUESTED); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Middle.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Middle.java new file mode 100644 index 000000000..f4f184cc1 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Middle.java @@ -0,0 +1,29 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; + +/** + * @author manuelgu, altered by Citymonstret + */ +@CommandDeclaration(command = "middle", aliases = {"center", "centre"}, + description = "Teleports you to the center of the plot", usage = "/plot middle", + category = CommandCategory.TELEPORT, requiredType = RequiredType.NONE) public class Middle + extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] arguments) { + Location location = player.getLocation(); + Plot plot = location.getPlot(); + if (plot == null) { + return sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + return sendMessage(player, C.PLOT_UNOWNED); + } + player.teleport(plot.getCenter()); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Move.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Move.java new file mode 100644 index 000000000..289a3c4a6 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Move.java @@ -0,0 +1,65 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotArea; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; + +@CommandDeclaration(usage = "/plot move ", command = "move", description = "Move a plot", + aliases = {"debugmove"}, permission = "plots.move", category = CommandCategory.CLAIMING, + requiredType = RequiredType.NONE) public class Move extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + Location loc = player.getLocation(); + Plot plot1 = loc.getPlotAbs(); + if (plot1 == null) { + return !MainUtil.sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot1.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN.s())) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + boolean override = false; + if (args.length == 2 && args[1].equalsIgnoreCase("-f")) { + args = new String[] {args[0]}; + override = true; + } + if (args.length != 1) { + C.COMMAND_SYNTAX.send(player, getUsage()); + return false; + } + PlotArea area = PlotSquared.get().getPlotAreaByString(args[0]); + Plot plot2; + if (area == null) { + plot2 = MainUtil.getPlotFromString(player, args[0], true); + if (plot2 == null) { + return false; + } + } else { + plot2 = area.getPlotAbs(plot1.getId()); + } + if (plot1.equals(plot2)) { + MainUtil.sendMessage(player, C.NOT_VALID_PLOT_ID); + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot copy "); + return false; + } + if (!plot1.getArea().isCompatible(plot2.getArea()) && (!override || !Permissions + .hasPermission(player, C.PERMISSION_ADMIN.s()))) { + C.PLOTWORLD_INCOMPATIBLE.send(player); + return false; + } + if (plot1.move(plot2, () -> MainUtil.sendMessage(player, C.MOVE_SUCCESS), false)) { + return true; + } else { + MainUtil.sendMessage(player, C.REQUIRES_UNOWNED); + return false; + } + } + +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Music.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Music.java new file mode 100644 index 000000000..b2e5170e5 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Music.java @@ -0,0 +1,69 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.flag.Flags; +import com.github.intellectualsites.plotsquared.plot.object.*; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Locale; + +@CommandDeclaration(command = "music", permission = "plots.music", + description = "Play music in your plot", usage = "/plot music", + category = CommandCategory.APPEARANCE, requiredType = RequiredType.PLAYER) public class Music + extends SubCommand { + + private static final Collection DISCS = Arrays + .asList("music_disc_13", "music_disc_cat", "music_disc_blocks", "music_disc_chirp", + "music_disc_far", "music_disc_mall", "music_disc_mellohi", "music_disc_stal", + "music_disc_strad", "music_disc_ward", "music_disc_11", "music_disc_wait"); + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + Location loc = player.getLocation(); + final Plot plot = loc.getPlotAbs(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.isAdded(player.getUUID())) { + sendMessage(player, C.NO_PLOT_PERMS); + return true; + } + PlotInventory inv = new PlotInventory(player, 2, "Plot Jukebox") { + @Override public boolean onClick(int index) { + PlotItemStack item = getItem(index); + if (item == null) { + return true; + } + if (item.getPlotBlock().equalsAny(7, "bedrock")) { + plot.removeFlag(Flags.MUSIC); + C.FLAG_REMOVED.send(player); + } else if (item.name.toLowerCase(Locale.ENGLISH).contains("disc")) { + plot.setFlag(Flags.MUSIC, item.getPlotBlock().getRawId()); + C.FLAG_ADDED.send(player); + } else { + C.FLAG_NOT_ADDED.send(player); + } + return false; + } + }; + int index = 0; + + for (final String disc : DISCS) { + final String name = String.format("&r&6%s", disc); + final String[] lore = {"&r&aClick to play!"}; + final PlotItemStack item = new PlotItemStack(disc, 1, name, lore); + inv.setItem(index++, item); + } + + // Always add the cancel button + // if (player.getMeta("music") != null) { + String name = "&r&6Cancel music"; + String[] lore = {"&r&cClick to cancel!"}; + inv.setItem(index, new PlotItemStack("bedrock", 1, name, lore)); + // } + + inv.openInventory(); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Near.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Near.java new file mode 100644 index 000000000..c3e9c5f9e --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Near.java @@ -0,0 +1,24 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal2; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal3; +import com.github.intellectualsites.plotsquared.plot.util.StringMan; + +@CommandDeclaration(command = "near", aliases = "n", description = "Display nearby players", + usage = "/plot near", category = CommandCategory.INFO) public class Near extends Command { + public Near() { + super(MainCommand.getInstance(), true); + } + + @Override public void execute(PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) throws CommandException { + final Plot plot = check(player.getCurrentPlot(), C.NOT_IN_PLOT); + C.PLOT_NEAR.send(player, StringMan.join(plot.getPlayersInPlot(), ", ")); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Owner.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Owner.java new file mode 100644 index 000000000..4095c15c9 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Owner.java @@ -0,0 +1,96 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.*; + +import java.util.Set; +import java.util.UUID; + +@CommandDeclaration(command = "setowner", permission = "plots.set.owner", + description = "Set the plot owner", usage = "/plot setowner ", + aliases = {"owner", "so", "seto"}, category = CommandCategory.CLAIMING, + requiredType = RequiredType.NONE, confirmation = true) public class Owner extends SetCommand { + + @Override public boolean set(final PlotPlayer player, final Plot plot, String value) { + Set plots = plot.getConnectedPlots(); + UUID uuid = null; + String name = null; + if (value.length() == 36) { + try { + uuid = UUID.fromString(value); + name = MainUtil.getName(uuid); + } catch (Exception ignored) { + } + } else { + uuid = UUIDHandler.getUUID(value, null); + name = UUIDHandler.getName(uuid); + name = name == null ? value : name; + } + if (uuid == null || value.equalsIgnoreCase("-")) { + if (value.equalsIgnoreCase("none") || value.equalsIgnoreCase("null") || value + .equalsIgnoreCase("-")) { + if (!Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_SETOWNER.s(), true)) { + return false; + } + Set connected = plot.getConnectedPlots(); + plot.unlinkPlot(false, false); + for (Plot current : connected) { + current.unclaim(); + current.removeSign(); + } + MainUtil.sendMessage(player, C.SET_OWNER); + return true; + } + C.INVALID_PLAYER.send(player, value); + return false; + } + final PlotPlayer other = UUIDHandler.getPlayer(uuid); + if (plot.isOwner(uuid)) { + C.ALREADY_OWNER.send(player, MainUtil.getName(uuid)); + return false; + } + if (!Permissions.hasPermission(player, C.PERMISSION_ADMIN_COMMAND_SETOWNER)) { + if (other == null) { + C.INVALID_PLAYER_OFFLINE.send(player, value); + return false; + } + int size = plots.size(); + int currentPlots = (Settings.Limit.GLOBAL ? + other.getPlotCount() : + other.getPlotCount(plot.getWorldName())) + size; + if (currentPlots > other.getAllowedPlots()) { + sendMessage(player, C.CANT_TRANSFER_MORE_PLOTS); + return false; + } + } + final String finalName = name; + final UUID finalUUID = uuid; + final boolean removeDenied = plot.isDenied(finalUUID); + Runnable run = new Runnable() { + @Override public void run() { + if (plot.setOwner(finalUUID, player)) { + if (removeDenied) + plot.removeDenied(finalUUID); + plot.setSign(finalName); + MainUtil.sendMessage(player, C.SET_OWNER); + if (other != null) { + MainUtil + .sendMessage(other, C.NOW_OWNER, plot.getArea() + ";" + plot.getId()); + } + } else + MainUtil.sendMessage(player, C.SET_OWNER_CANCELLED); + } + }; + if (hasConfirmation(player)) { + CmdConfirm.addPending(player, "/plot set owner " + value, run); + } else { + TaskManager.runTask(run); + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/PluginCmd.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/PluginCmd.java new file mode 100644 index 000000000..81047ec9b --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/PluginCmd.java @@ -0,0 +1,38 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.json.JSONObject; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.HttpUtil; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.TaskManager; + +@CommandDeclaration(command = "plugin", permission = "plots.use", + description = "Show plugin information", usage = "/plot plugin", aliases = "version", + category = CommandCategory.INFO) public class PluginCmd extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + TaskManager.IMP.taskAsync(new Runnable() { + @Override public void run() { + MainUtil.sendMessage(player, String.format( + "$2>> $1&l" + PlotSquared.imp().getPluginName() + " $2($1Version$2: $1%s$2)", + PlotSquared.get().getVersion())); + MainUtil.sendMessage(player, + "$2>> $1&lAuthors$2: $1Citymonstret $2& $1Empire92 $2& $1MattBDev $2& $1dordsor21"); + MainUtil.sendMessage(player, + "$2>> $1&lWiki$2: $1https://github.com/IntellectualCrafters/PlotSquared/wiki"); + MainUtil.sendMessage(player, + "$2>> $1&lNewest Version$2: $1" + getNewestVersionString()); + } + }); + return true; + } + + public String getNewestVersionString() { + String str = HttpUtil + .readUrl("https://api.github.com/repos/IntellectualSites/PlotSquared/releases/latest"); + JSONObject release = new JSONObject(str); + return release.getString("name"); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Purge.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Purge.java new file mode 100644 index 000000000..8cd4d9e56 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Purge.java @@ -0,0 +1,167 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.listener.PlotListener; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotArea; +import com.github.intellectualsites.plotsquared.plot.object.PlotId; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.CmdConfirm; +import com.github.intellectualsites.plotsquared.plot.util.StringMan; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.UUID; + +@CommandDeclaration( + usage = "/plot purge world: area: id: owner: shared: unknown:[true|false]", + command = "purge", permission = "plots.admin", description = "Purge all plots for a world", + category = CommandCategory.ADMINISTRATION, requiredType = RequiredType.CONSOLE, + confirmation = true) public class Purge extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + if (args.length == 0) { + return false; + } + + String world = null; + PlotArea area = null; + PlotId id = null; + UUID owner = null; + UUID added = null; + boolean unknown = false; + for (String arg : args) { + String[] split = arg.split(":"); + if (split.length != 2) { + C.COMMAND_SYNTAX.send(player, getUsage()); + return false; + } + switch (split[0].toLowerCase()) { + case "world": + case "w": + world = split[1]; + break; + case "area": + case "a": + area = PlotSquared.get().getPlotAreaByString(split[1]); + if (area == null) { + C.NOT_VALID_PLOT_WORLD.send(player, split[1]); + return false; + } + break; + case "plotid": + case "id": + id = PlotId.fromString(split[1]); + if (id == null) { + C.NOT_VALID_PLOT_ID.send(player, split[1]); + return false; + } + break; + case "owner": + case "o": + owner = UUIDHandler.getUUID(split[1], null); + if (owner == null) { + C.INVALID_PLAYER.send(player, split[1]); + return false; + } + break; + case "shared": + case "s": + added = UUIDHandler.getUUID(split[1], null); + if (added == null) { + C.INVALID_PLAYER.send(player, split[1]); + return false; + } + break; + case "unknown": + case "?": + case "u": + unknown = Boolean.parseBoolean(split[1]); + break; + default: + C.COMMAND_SYNTAX.send(player, getUsage()); + return false; + } + } + final HashSet toDelete = new HashSet<>(); + for (Plot plot : PlotSquared.get().getBasePlots()) { + if (world != null && !plot.getWorldName().equalsIgnoreCase(world)) { + continue; + } + if (area != null && !plot.getArea().equals(area)) { + continue; + } + if (id != null && !plot.getId().equals(id)) { + continue; + } + if (owner != null && !plot.isOwner(owner)) { + continue; + } + if (added != null && !plot.isAdded(added)) { + continue; + } + if (unknown && UUIDHandler.getName(plot.owner) != null) { + continue; + } + toDelete.addAll(plot.getConnectedPlots()); + } + if (PlotSquared.get().plots_tmp != null) { + for (Entry> entry : PlotSquared.get().plots_tmp + .entrySet()) { + String worldName = entry.getKey(); + if (world != null && !world.equalsIgnoreCase(worldName)) { + continue; + } + for (Entry entry2 : entry.getValue().entrySet()) { + Plot plot = entry2.getValue(); + if (id != null && !plot.getId().equals(id)) { + continue; + } + if (owner != null && !plot.isOwner(owner)) { + continue; + } + if (added != null && !plot.isAdded(added)) { + continue; + } + if (unknown && UUIDHandler.getName(plot.owner) != null) { + continue; + } + toDelete.add(plot); + } + } + } + if (toDelete.isEmpty()) { + C.FOUND_NO_PLOTS.send(player); + return false; + } + String cmd = + "/plot purge " + StringMan.join(args, " ") + " (" + toDelete.size() + " plots)"; + Runnable run = () -> { + PlotSquared.debug("Calculating plots to purge, please wait..."); + HashSet ids = new HashSet<>(); + for (Plot plot : toDelete) { + if (plot.temp != Integer.MAX_VALUE) { + ids.add(plot.temp); + plot.getArea().removePlot(plot.getId()); + for (PlotPlayer pp : plot.getPlayersInPlot()) { + PlotListener.plotEntry(pp, plot); + } + plot.removeSign(); + } + } + DBFunc.purgeIds(ids); + C.PURGE_SUCCESS.send(player, ids.size() + "/" + toDelete.size()); + }; + if (hasConfirmation(player)) { + CmdConfirm.addPending(player, cmd, run); + } else { + run.run(); + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Rate.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Rate.java new file mode 100644 index 000000000..d8103e5b8 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Rate.java @@ -0,0 +1,217 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.flag.Flags; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.UUID; + +@CommandDeclaration(command = "rate", permission = "plots.rate", description = "Rate the plot", + usage = "/plot rate [#|next|purge]", aliases = "rt", category = CommandCategory.INFO, + requiredType = RequiredType.NONE) public class Rate extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + if (args.length == 1) { + switch (args[0].toLowerCase()) { + case "next": { + ArrayList plots = new ArrayList<>(PlotSquared.get().getBasePlots()); + plots.sort((p1, p2) -> { + double v1 = 0; + if (!p1.getRatings().isEmpty()) { + for (Entry entry : p1.getRatings().entrySet()) { + v1 -= 11 - entry.getValue().getAverageRating(); + } + } + double v2 = 0; + if (!p2.getRatings().isEmpty()) { + for (Entry entry : p2.getRatings().entrySet()) { + v2 -= 11 - entry.getValue().getAverageRating(); + } + } + if (v1 == v2) { + return -0; + } + return v2 > v1 ? 1 : -1; + }); + UUID uuid = player.getUUID(); + for (Plot p : plots) { + if ((!Settings.Done.REQUIRED_FOR_RATINGS || p.hasFlag(Flags.DONE)) && p + .isBasePlot() && (!p.getRatings().containsKey(uuid)) && !p + .isAdded(uuid)) { + p.teleportPlayer(player); + MainUtil.sendMessage(player, C.RATE_THIS); + return true; + } + } + MainUtil.sendMessage(player, C.FOUND_NO_PLOTS); + return false; + } + case "purge": { + final Plot plot = player.getCurrentPlot(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!Permissions.hasPermission(player, C.PERMISSION_ADMIN_COMMAND_RATE, true)) { + return false; + } + plot.clearRatings(); + C.RATINGS_PURGED.send(player); + return true; + } + } + } + final Plot plot = player.getCurrentPlot(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + sendMessage(player, C.RATING_NOT_OWNED); + return false; + } + if (plot.isOwner(player.getUUID())) { + sendMessage(player, C.RATING_NOT_YOUR_OWN); + return false; + } + if (Settings.Done.REQUIRED_FOR_RATINGS && !plot.hasFlag(Flags.DONE)) { + sendMessage(player, C.RATING_NOT_DONE); + return false; + } + if (Settings.Ratings.CATEGORIES != null && !Settings.Ratings.CATEGORIES.isEmpty()) { + final Runnable run = new Runnable() { + @Override public void run() { + if (plot.getRatings().containsKey(player.getUUID())) { + sendMessage(player, C.RATING_ALREADY_EXISTS, plot.getId().toString()); + return; + } + final MutableInt index = new MutableInt(0); + final MutableInt rating = new MutableInt(0); + String title = Settings.Ratings.CATEGORIES.get(0); + PlotInventory inventory = new PlotInventory(player, 1, title) { + @Override public boolean onClick(int i) { + rating.add((i + 1) * Math.pow(10, index.getValue())); + index.increment(); + if (index.getValue() >= Settings.Ratings.CATEGORIES.size()) { + int rV = rating.getValue(); + Rating result = + EventUtil.manager.callRating(this.player, plot, new Rating(rV)); + if (result != null) { + plot.addRating(this.player.getUUID(), result); + sendMessage(this.player, C.RATING_APPLIED, + plot.getId().toString()); + if (Permissions + .hasPermission(this.player, C.PERMISSION_COMMENT)) { + Command command = + MainCommand.getInstance().getCommand(Comment.class); + if (command != null) { + MainUtil.sendMessage(this.player, C.COMMENT_THIS, + command.getUsage()); + } + } + } + return false; + } + setTitle(Settings.Ratings.CATEGORIES.get(index.getValue())); + return true; + } + }; + inventory.setItem(0, new PlotItemStack("minecraft:brown_wool", 0, "0/8")); + inventory.setItem(1, new PlotItemStack(35, (short) 14, 1, "1/8")); + inventory.setItem(2, new PlotItemStack(35, (short) 1, 2, "2/8")); + inventory.setItem(3, new PlotItemStack(35, (short) 4, 3, "3/8")); + inventory.setItem(4, new PlotItemStack(35, (short) 5, 4, "4/8")); + inventory.setItem(5, new PlotItemStack(35, (short) 9, 5, "5/8")); + inventory.setItem(6, new PlotItemStack(35, (short) 11, 6, "6/8")); + inventory.setItem(7, new PlotItemStack(35, (short) 10, 7, "7/8")); + inventory.setItem(8, new PlotItemStack(35, (short) 2, 8, "8/8")); + inventory.openInventory(); + } + }; + if (plot.getSettings().ratings == null) { + if (!Settings.Enabled_Components.RATING_CACHE) { + TaskManager.runTaskAsync(() -> { + plot.getSettings().ratings = DBFunc.getRatings(plot); + run.run(); + }); + return true; + } + plot.getSettings().ratings = new HashMap<>(); + } + run.run(); + return true; + } + if (args.length < 1) { + sendMessage(player, C.RATING_NOT_VALID); + return true; + } + String arg = args[0]; + final int rating; + if (MathMan.isInteger(arg) && arg.length() < 3 && !arg.isEmpty()) { + rating = Integer.parseInt(arg); + if (rating > 10 || rating < 1) { + sendMessage(player, C.RATING_NOT_VALID); + return false; + } + } else { + sendMessage(player, C.RATING_NOT_VALID); + return false; + } + final UUID uuid = player.getUUID(); + final Runnable run = () -> { + if (plot.getRatings().containsKey(uuid)) { + sendMessage(player, C.RATING_ALREADY_EXISTS, plot.getId().toString()); + return; + } + Rating result = EventUtil.manager.callRating(player, plot, new Rating(rating)); + if (result != null) { + plot.addRating(uuid, result); + sendMessage(player, C.RATING_APPLIED, plot.getId().toString()); + } + }; + if (plot.getSettings().ratings == null) { + if (!Settings.Enabled_Components.RATING_CACHE) { + TaskManager.runTaskAsync(() -> { + plot.getSettings().ratings = DBFunc.getRatings(plot); + run.run(); + }); + return true; + } + plot.getSettings().ratings = new HashMap<>(); + } + run.run(); + return true; + } + + private class MutableInt { + + private int value; + + MutableInt(int i) { + this.value = i; + } + + void increment() { + this.value++; + } + + void decrement() { + this.value--; + } + + int getValue() { + return this.value; + } + + void add(Number v) { + this.value += v.intValue(); + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/RegenAllRoads.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/RegenAllRoads.java new file mode 100644 index 000000000..9edf20a62 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/RegenAllRoads.java @@ -0,0 +1,60 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.generator.HybridPlotManager; +import com.github.intellectualsites.plotsquared.plot.generator.HybridUtils; +import com.github.intellectualsites.plotsquared.plot.object.PlotArea; +import com.github.intellectualsites.plotsquared.plot.object.PlotManager; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; + +@CommandDeclaration(command = "regenallroads", + description = "Regenerate all roads in the map using the set road schematic", + aliases = {"rgar"}, usage = "/plot regenallroads [height]", + category = CommandCategory.ADMINISTRATION, requiredType = RequiredType.CONSOLE, + permission = "plots.regenallroads") public class RegenAllRoads extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + int height = 0; + if (args.length == 2) { + try { + height = Integer.parseInt(args[1]); + } catch (NumberFormatException ignored) { + MainUtil.sendMessage(player, C.NOT_VALID_NUMBER, "(0, 256)"); + MainUtil + .sendMessage(player, C.COMMAND_SYNTAX, "/plot regenallroads [height]"); + return false; + } + } else if (args.length != 1) { + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot regenallroads [height]"); + return false; + } + PlotArea area = PlotSquared.get().getPlotAreaByString(args[0]); + if (area == null) { + C.NOT_VALID_PLOT_WORLD.send(player, args[0]); + return false; + } + String name = args[0]; + PlotManager manager = area.getPlotManager(); + if (!(manager instanceof HybridPlotManager)) { + MainUtil.sendMessage(player, C.NOT_VALID_PLOT_WORLD); + return false; + } + //Set chunks = ChunkManager.manager.getChunkChunks(name); + MainUtil + .sendMessage(player, "&cIf no schematic is set, the following will not do anything"); + MainUtil.sendMessage(player, + "&7 - To set a schematic, stand in a plot and use &c/plot createroadschematic"); + //MainUtil.sendMessage(player, "&6Potential chunks to update: &7" + (chunks.size() * 1024)); + //MainUtil.sendMessage(player, "&6Estimated time: &7" + chunks.size() + " seconds"); + boolean result = HybridUtils.manager.scheduleRoadUpdate(area, height); + if (!result) { + MainUtil.sendMessage(player, + "&cCannot schedule mass schematic update! (Is one already in progress?)"); + return false; + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Relight.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Relight.java new file mode 100644 index 000000000..79a87b887 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Relight.java @@ -0,0 +1,39 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.ChunkManager; +import com.github.intellectualsites.plotsquared.plot.util.block.LocalBlockQueue; + +import java.util.HashSet; + +@CommandDeclaration(command = "relight", description = "Relight your plot", usage = "/plot relight", + category = CommandCategory.DEBUG) public class Relight extends Command { + public Relight() { + super(MainCommand.getInstance(), true); + } + + @Override public void execute(final PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) { + final Plot plot = player.getCurrentPlot(); + if (plot == null) { + C.NOT_IN_PLOT.send(player); + return; + } + HashSet regions = plot.getRegions(); + final LocalBlockQueue queue = plot.getArea().getQueue(false); + ChunkManager.chunkTask(plot, new RunnableVal() { + @Override public void run(int[] value) { + queue.fixChunkLighting(value[0], value[1]); + } + }, new Runnable() { + @Override public void run() { + plot.refreshChunks(); + C.SET_BLOCK_ACTION_FINISHED.send(player); + } + }, 5); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Reload.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Reload.java new file mode 100644 index 000000000..71ee2e840 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Reload.java @@ -0,0 +1,84 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.configuration.ConfigurationSection; +import com.github.intellectualsites.plotsquared.configuration.MemorySection; +import com.github.intellectualsites.plotsquared.configuration.file.YamlConfiguration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.PlotArea; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; + +import java.io.IOException; +import java.util.Objects; + +@CommandDeclaration(command = "reload", aliases = "rl", permission = "plots.admin.command.reload", + description = "Reload translations and world settings", usage = "/plot reload", + category = CommandCategory.ADMINISTRATION) public class Reload extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + try { + // The following won't affect world generation, as that has to be + // loaded during startup unfortunately. + PlotSquared.get().setupConfigs(); + C.load(PlotSquared.get().translationFile); + PlotSquared.get().foreachPlotArea(new RunnableVal() { + @Override public void run(PlotArea area) { + ConfigurationSection worldSection = PlotSquared.get().worlds + .getConfigurationSection("worlds." + area.worldname); + if (worldSection == null) { + return; + } + if (area.TYPE != 2 || !worldSection.contains("areas")) { + area.saveConfiguration(worldSection); + area.loadDefaultConfiguration(worldSection); + } else { + ConfigurationSection areaSection = worldSection.getConfigurationSection( + "areas." + area.id + "-" + area.getMin() + "-" + area.getMax()); + YamlConfiguration clone = new YamlConfiguration(); + for (String key : areaSection.getKeys(true)) { + if (areaSection.get(key) instanceof MemorySection) { + continue; + } + if (!clone.contains(key)) { + clone.set(key, areaSection.get(key)); + } + } + for (String key : worldSection.getKeys(true)) { + if (worldSection.get(key) instanceof MemorySection) { + continue; + } + if (!key.startsWith("areas") && !clone.contains(key)) { + clone.set(key, worldSection.get(key)); + } + } + area.saveConfiguration(clone); + // netSections is the combination of + for (String key : clone.getKeys(true)) { + if (clone.get(key) instanceof MemorySection) { + continue; + } + if (!worldSection.contains(key)) { + worldSection.set(key, clone.get(key)); + } else { + Object value = worldSection.get(key); + if (Objects.equals(value, clone.get(key))) { + areaSection.set(key, clone.get(key)); + } + } + } + area.loadDefaultConfiguration(clone); + } + } + }); + PlotSquared.get().worlds.save(PlotSquared.get().worldsFile); + MainUtil.sendMessage(player, C.RELOADED_CONFIGS); + } catch (IOException e) { + e.printStackTrace(); + MainUtil.sendMessage(player, C.RELOAD_FAILED); + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Remove.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Remove.java new file mode 100644 index 000000000..bf027f20d --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Remove.java @@ -0,0 +1,108 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Argument; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.database.DBFunc; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.EventUtil; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; +import com.github.intellectualsites.plotsquared.plot.util.UUIDHandler; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +@CommandDeclaration(command = "remove", aliases = {"r", "untrust", "ut", "undeny", "unban", "ud"}, + description = "Remove a player from a plot", usage = "/plot remove ", + category = CommandCategory.SETTINGS, requiredType = RequiredType.NONE, + permission = "plots.remove") public class Remove extends SubCommand { + + public Remove() { + super(Argument.PlayerName); + } + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + Location location = player.getLocation(); + Plot plot = location.getPlotAbs(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + MainUtil.sendMessage(player, C.PLOT_UNOWNED); + return false; + } + if (!plot.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_REMOVE)) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return true; + } + int count = 0; + switch (args[0]) { + case "unknown": { + ArrayList toRemove = new ArrayList<>(); + HashSet all = new HashSet<>(); + all.addAll(plot.getMembers()); + all.addAll(plot.getTrusted()); + all.addAll(plot.getDenied()); + for (UUID uuid : all) { + if (UUIDHandler.getName(uuid) == null) { + toRemove.add(uuid); + count++; + } + } + for (UUID uuid : toRemove) { + plot.removeDenied(uuid); + plot.removeTrusted(uuid); + plot.removeMember(uuid); + } + break; + } + default: + Set uuids = MainUtil.getUUIDsFromString(args[0]); + if (!uuids.isEmpty()) { + for (UUID uuid : uuids) { + if (uuid == DBFunc.EVERYONE) { + if (plot.removeTrusted(uuid)) { + EventUtil.manager.callTrusted(player, plot, uuid, false); + count++; + } else if (plot.removeMember(uuid)) { + EventUtil.manager.callMember(player, plot, uuid, false); + count++; + } else if (plot.removeDenied(uuid)) { + EventUtil.manager.callDenied(player, plot, uuid, false); + count++; + } + } else if (plot.getTrusted().contains(uuid)) { + if (plot.removeTrusted(uuid)) { + EventUtil.manager.callTrusted(player, plot, uuid, false); + count++; + } + } else if (plot.getMembers().contains(uuid)) { + if (plot.removeMember(uuid)) { + EventUtil.manager.callMember(player, plot, uuid, false); + count++; + } + } else if (plot.getDenied().contains(uuid)) { + if (plot.removeDenied(uuid)) { + EventUtil.manager.callDenied(player, plot, uuid, false); + count++; + } + } + } + } + break; + } + if (count == 0) { + MainUtil.sendMessage(player, C.INVALID_PLAYER, args[0]); + return false; + } else { + MainUtil.sendMessage(player, C.REMOVED_PLAYERS, count + ""); + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/RequiredType.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/RequiredType.java new file mode 100644 index 000000000..3f13368de --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/RequiredType.java @@ -0,0 +1,16 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandCaller; + +public enum RequiredType { + CONSOLE, PLAYER, NONE; + + public boolean allows(CommandCaller player) { + switch (this) { + case NONE: + return true; + default: + return this == player.getSuperCaller(); + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Save.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Save.java new file mode 100644 index 000000000..aec9543ed --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Save.java @@ -0,0 +1,76 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; +import com.github.intellectualsites.plotsquared.plot.util.SchematicHandler; +import com.github.intellectualsites.plotsquared.plot.util.TaskManager; +import com.sk89q.jnbt.CompoundTag; + +import java.net.URL; +import java.util.List; +import java.util.UUID; + +@CommandDeclaration(command = "save", aliases = {"backup"}, description = "Save your plot", + category = CommandCategory.SCHEMATIC, requiredType = RequiredType.NONE, + permission = "plots.save") public class Save extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + String world = player.getLocation().getWorld(); + if (!PlotSquared.get().hasPlotArea(world)) { + return !sendMessage(player, C.NOT_IN_PLOT_WORLD); + } + final Plot plot = player.getCurrentPlot(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + MainUtil.sendMessage(player, C.PLOT_UNOWNED); + return false; + } + if (!plot.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_SAVE)) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + if (plot.getRunning() > 0) { + MainUtil.sendMessage(player, C.WAIT_FOR_TIMER); + return false; + } + plot.addRunning(); + SchematicHandler.manager.getCompoundTag(plot, new RunnableVal() { + @Override public void run(final CompoundTag value) { + TaskManager.runTaskAsync(() -> { + String time = (System.currentTimeMillis() / 1000) + ""; + Location[] corners = plot.getCorners(); + corners[0].setY(0); + corners[1].setY(255); + int size = (corners[1].getX() - corners[0].getX()) + 1; + PlotId id = plot.getId(); + String world1 = plot.getArea().toString().replaceAll(";", "-") + .replaceAll("[^A-Za-z0-9]", ""); + final String file = time + '_' + world1 + '_' + id.x + '_' + id.y + '_' + size; + UUID uuid = player.getUUID(); + SchematicHandler.manager.upload(value, uuid, file, new RunnableVal() { + @Override public void run(URL url) { + plot.removeRunning(); + if (url == null) { + MainUtil.sendMessage(player, C.SAVE_FAILED); + return; + } + MainUtil.sendMessage(player, C.SAVE_SUCCESS); + List schematics = player.getMeta("plot_schematics"); + if (schematics != null) { + schematics.add(file + ".schematic"); + } + } + }); + }); + } + }); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SchematicCmd.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SchematicCmd.java new file mode 100644 index 000000000..110992543 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SchematicCmd.java @@ -0,0 +1,222 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.object.schematic.Schematic; +import com.github.intellectualsites.plotsquared.plot.util.*; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; + +@CommandDeclaration(command = "schematic", permission = "plots.schematic", + description = "Schematic command", aliases = {"sch", "schem"}, + category = CommandCategory.SCHEMATIC, usage = "/plot schematic ") +public class SchematicCmd extends SubCommand { + + private boolean running = false; + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + if (args.length < 1) { + sendMessage(player, C.SCHEMATIC_MISSING_ARG); + return true; + } + String arg = args[0].toLowerCase(); + switch (arg) { + case "paste": { + if (!Permissions.hasPermission(player, C.PERMISSION_SCHEMATIC_PASTE)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_SCHEMATIC_PASTE); + return false; + } + if (args.length < 2) { + sendMessage(player, C.SCHEMATIC_MISSING_ARG); + break; + } + Location loc = player.getLocation(); + final Plot plot = loc.getPlotAbs(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + MainUtil.sendMessage(player, C.PLOT_UNOWNED); + return false; + } + if (!plot.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_SCHEMATIC_PASTE)) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + if (this.running) { + MainUtil.sendMessage(player, "&cTask is already running."); + return false; + } + final String location = args[1]; + this.running = true; + TaskManager.runTaskAsync(() -> { + Schematic schematic = null; + if (location.startsWith("url:")) { + try { + UUID uuid = UUID.fromString(location.substring(4)); + URL base = new URL(Settings.Web.URL); + URL url = new URL(base, "uploads/" + uuid + ".schematic"); + schematic = SchematicHandler.manager.getSchematic(url); + } catch (Exception e) { + e.printStackTrace(); + sendMessage(player, C.SCHEMATIC_INVALID, + "non-existent url: " + location); + SchematicCmd.this.running = false; + return; + } + } else { + try { + schematic = SchematicHandler.manager.getSchematic(location); + } catch (SchematicHandler.UnsupportedFormatException e) { + e.printStackTrace(); + } + } + if (schematic == null) { + SchematicCmd.this.running = false; + sendMessage(player, C.SCHEMATIC_INVALID, + "non-existent or not in gzip format"); + return; + } + SchematicHandler.manager + .paste(schematic, plot, 0, 1, 0, false, new RunnableVal() { + @Override public void run(Boolean value) { + SchematicCmd.this.running = false; + if (value) { + sendMessage(player, C.SCHEMATIC_PASTE_SUCCESS); + } else { + sendMessage(player, C.SCHEMATIC_PASTE_FAILED); + } + } + }); + }); + break; + } + // TODO test + // case "test": { + // if (!Permissions.hasPermission(plr, "plots.schematic.test")) { + // MainUtil.sendMessage(plr, C.NO_PERMISSION, "plots.schematic.test"); + // return false; + // } + // if (args.length < 2) { + // sendMessage(plr, C.SCHEMATIC_MISSING_ARG); + // return false; + // } + // final Location loc = plr.getLocation(); + // final Plot plot = MainUtil.getPlot(loc); + // if (plot == null) { + // sendMessage(plr, C.NOT_IN_PLOT); + // return false; + // } + // file = args[1]; + // schematic = SchematicHandler.manager.getSchematic(file); + // if (schematic == null) { + // sendMessage(plr, C.SCHEMATIC_INVALID, "non-existent"); + // return false; + // } + // final int l1 = schematic.getSchematicDimension().getX(); + // final int l2 = schematic.getSchematicDimension().getZ(); + // final int length = MainUtil.getPlotWidth(loc.getWorld(), plot.id); + // if ((l1 < length) || (l2 < length)) { + // sendMessage(plr, C.SCHEMATIC_INVALID, String.format("Wrong size (x: %s, z: %d) vs %d ", l1, l2, length)); + // break; + // } + // sendMessage(plr, C.SCHEMATIC_VALID); + // break; + // } + case "saveall": + case "exportall": { + if (!(player instanceof ConsolePlayer)) { + MainUtil.sendMessage(player, C.NOT_CONSOLE); + return false; + } + if (args.length != 2) { + MainUtil.sendMessage(player, + "&cNeed world argument. Use &7/plot sch exportall "); + return false; + } + PlotArea area = PlotSquared.get().getPlotAreaByString(args[1]); + if (area == null) { + C.NOT_VALID_PLOT_WORLD.send(player, args[1]); + return false; + } + Collection plots = area.getPlots(); + if (plots.isEmpty()) { + MainUtil + .sendMessage(player, "&cInvalid world. Use &7/plot sch exportall "); + return false; + } + boolean result = SchematicHandler.manager.exportAll(plots, null, null, + () -> MainUtil.sendMessage(player, "&aFinished mass export")); + if (!result) { + MainUtil.sendMessage(player, "&cTask is already running."); + return false; + } else { + MainUtil.sendMessage(player, + "&3Plot&8->&3Schematic&8: &7Mass export has started. This may take a while."); + MainUtil.sendMessage(player, + "&3Plot&8->&3Schematic&8: &7Found &c" + plots.size() + "&7 plots..."); + } + break; + } + case "export": + case "save": + if (!Permissions.hasPermission(player, C.PERMISSION_SCHEMATIC_SAVE)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_SCHEMATIC_SAVE); + return false; + } + if (this.running) { + MainUtil.sendMessage(player, "&cTask is already running."); + return false; + } + Location location = player.getLocation(); + Plot plot = location.getPlotAbs(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + MainUtil.sendMessage(player, C.PLOT_UNOWNED); + return false; + } + if (!plot.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN_COMMAND_SCHEMATIC_SAVE)) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + location.getWorld(); + Collection plots = new ArrayList<>(); + plots.add(plot); + boolean result = SchematicHandler.manager.exportAll(plots, null, null, () -> { + MainUtil.sendMessage(player, "&aFinished export"); + SchematicCmd.this.running = false; + }); + if (!result) { + MainUtil.sendMessage(player, "&cTask is already running."); + return false; + } else { + MainUtil.sendMessage(player, "&7Starting export..."); + } + break; + case "list": { + if (!Permissions.hasPermission(player, C.PERMISSION_SCHEMATIC_LIST)) { + MainUtil.sendMessage(player, C.NO_PERMISSION, C.PERMISSION_SCHEMATIC_LIST); + return false; + } + final String string = + StringMan.join(SchematicHandler.manager.getShematicNames(), "$2, $1"); + C.SCHEMATIC_LIST.send(player, string); + } + break; + default: + sendMessage(player, C.SCHEMATIC_MISSING_ARG); + break; + } + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Set.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Set.java new file mode 100644 index 000000000..95166aa4a --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Set.java @@ -0,0 +1,167 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Configuration; +import com.github.intellectualsites.plotsquared.plot.config.Configuration.UnknownBlockException; +import com.github.intellectualsites.plotsquared.plot.flag.Flag; +import com.github.intellectualsites.plotsquared.plot.flag.FlagManager; +import com.github.intellectualsites.plotsquared.plot.flag.Flags; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.*; +import com.github.intellectualsites.plotsquared.plot.util.block.GlobalBlockQueue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +@CommandDeclaration(command = "set", description = "Set a plot value", aliases = {"s"}, + usage = "/plot set ", permission = "plots.set", + category = CommandCategory.APPEARANCE, requiredType = RequiredType.NONE) public class Set + extends SubCommand { + + public static final String[] values = new String[] {"biome", "alias", "home", "flag"}; + public static final String[] aliases = new String[] {"b", "w", "wf", "f", "a", "h", "fl"}; + + private final SetCommand component; + + public Set() { + this.component = new SetCommand() { + + @Override public String getId() { + return "set.component"; + } + + @Override public boolean set(PlotPlayer player, final Plot plot, String value) { + PlotArea plotArea = player.getLocation().getPlotArea(); + PlotManager manager = player.getLocation().getPlotManager(); + String[] components = manager.getPlotComponents(plotArea, plot.getId()); + boolean allowUnsafe = DebugAllowUnsafe.unsafeAllowed.contains(player.getUUID()); + + String[] args = value.split(" "); + String material = + StringMan.join(Arrays.copyOfRange(args, 1, args.length), ",").trim(); + + for (String component : components) { + if (component.equalsIgnoreCase(args[0])) { + if (!Permissions + .hasPermission(player, C.PERMISSION_SET_COMPONENT.f(component))) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_SET_COMPONENT.f(component)); + return false; + } + // PlotBlock[] blocks; + BlockBucket bucket; + try { + if (args.length < 2) { + MainUtil.sendMessage(player, C.NEED_BLOCK); + return true; + } + String[] split = material.split(","); + // blocks = Configuration.BLOCKLIST.parseString(material); + + try { + bucket = Configuration.BLOCK_BUCKET.parseString(material); + } catch (final UnknownBlockException unknownBlockException) { + final String unknownBlock = unknownBlockException.getUnknownValue(); + C.NOT_VALID_BLOCK.send(player, unknownBlock); + StringComparison.ComparisonResult match = + WorldUtil.IMP.getClosestBlock(unknownBlock); + if (match != null) { + final String found = + WorldUtil.IMP.getClosestMatchingName(match.best); + if (found != null) { + MainUtil.sendMessage(player, C.DID_YOU_MEAN, + found.toLowerCase()); + } + } + return false; + } + + if (!allowUnsafe) { + for (final PlotBlock block : bucket.getBlocks()) { + if (!block.isAir() && !WorldUtil.IMP.isBlockSolid(block)) { + C.NOT_ALLOWED_BLOCK.send(player, block.toString()); + return false; + } + } + } + } catch (Exception ignored) { + MainUtil.sendMessage(player, C.NOT_VALID_BLOCK, material); + return false; + } + if (plot.getRunning() > 0) { + MainUtil.sendMessage(player, C.WAIT_FOR_TIMER); + return false; + } + plot.addRunning(); + for (Plot current : plot.getConnectedPlots()) { + current.setComponent(component, bucket); + } + MainUtil.sendMessage(player, C.GENERATING_COMPONENT); + GlobalBlockQueue.IMP.addTask(new Runnable() { + @Override public void run() { + plot.removeRunning(); + } + }); + return true; + } + } + return false; + } + }; + } + + public boolean noArgs(PlotPlayer player) { + ArrayList newValues = new ArrayList<>(); + newValues.addAll(Arrays.asList("biome", "alias", "home", "flag")); + Plot plot = player.getCurrentPlot(); + if (plot != null) { + newValues.addAll( + Arrays.asList(plot.getManager().getPlotComponents(plot.getArea(), plot.getId()))); + } + MainUtil.sendMessage(player, C.SUBCOMMAND_SET_OPTIONS_HEADER.s() + StringMan + .join(newValues, C.BLOCK_LIST_SEPARATER.formatted())); + return false; + } + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + if (args.length == 0) { + return noArgs(player); + } + Command cmd = MainCommand.getInstance().getCommand("set" + args[0]); + if (cmd != null) { + if (!Permissions.hasPermission(player, cmd.getPermission(), true)) { + return false; + } + cmd.execute(player, Arrays.copyOfRange(args, 1, args.length), null, null); + return true; + } + // Additional checks + Plot plot = player.getCurrentPlot(); + if (plot == null) { + MainUtil.sendMessage(player, C.NOT_IN_PLOT); + return false; + } + // components + HashSet components = new HashSet<>( + Arrays.asList(plot.getManager().getPlotComponents(plot.getArea(), plot.getId()))); + if (components.contains(args[0].toLowerCase())) { + return this.component.onCommand(player, Arrays.copyOfRange(args, 0, args.length)); + } + // flag + Flag flag = FlagManager.getFlag(args[0].toLowerCase()); + if (Flags.getFlags().contains(flag)) { + StringBuilder a = new StringBuilder(); + if (args.length > 1) { + for (int x = 1; x < args.length; x++) { + a.append(" ").append(args[x]); + } + } + MainCommand.onCommand(player, ("flag set " + args[0] + a.toString()).split(" ")); + return true; + } + return noArgs(player); + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SetCommand.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SetCommand.java new file mode 100644 index 000000000..aaa1adfa2 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SetCommand.java @@ -0,0 +1,43 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; +import com.github.intellectualsites.plotsquared.plot.util.StringMan; + +public abstract class SetCommand extends SubCommand { + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + Location loc = player.getLocation(); + Plot plot = loc.getPlotAbs(); + if (plot == null) { + return !sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot.hasOwner()) { + if (!Permissions.hasPermission(player, C.PERMISSION_ADMIN_COMMAND.f(getFullId()))) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_ADMIN_COMMAND.f(getFullId())); + MainUtil.sendMessage(player, C.PLOT_NOT_CLAIMED); + return false; + } + } + if (!plot.isOwner(player.getUUID())) { + if (!Permissions.hasPermission(player, C.PERMISSION_ADMIN_COMMAND.f(getFullId()))) { + MainUtil.sendMessage(player, C.NO_PERMISSION, + C.PERMISSION_ADMIN_COMMAND.f(getFullId())); + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + } + if (args.length == 0) { + return set(player, plot, ""); + } + return set(player, plot, StringMan.join(args, " ")); + } + + public abstract boolean set(PlotPlayer player, Plot plot, String value); + +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SetHome.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SetHome.java new file mode 100644 index 000000000..fc92cc550 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SetHome.java @@ -0,0 +1,40 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.BlockLoc; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; + +@CommandDeclaration(command = "sethome", permission = "plots.set.home", + description = "Set the plot home to your current position", usage = "/plot sethome [none]", + aliases = {"sh", "seth"}, category = CommandCategory.SETTINGS, requiredType = RequiredType.NONE) +public class SetHome extends SetCommand { + + @Override public boolean set(PlotPlayer player, Plot plot, String value) { + switch (value.toLowerCase()) { + case "unset": + case "reset": + case "remove": + case "none": { + Plot base = plot.getBasePlot(false); + base.setHome(null); + return MainUtil.sendMessage(player, C.POSITION_UNSET); + } + case "": + Plot base = plot.getBasePlot(false); + Location bot = base.getBottomAbs(); + Location loc = player.getLocationFull(); + BlockLoc rel = + new BlockLoc(loc.getX() - bot.getX(), loc.getY(), loc.getZ() - bot.getZ(), + loc.getYaw(), loc.getPitch()); + base.setHome(rel); + return MainUtil.sendMessage(player, C.POSITION_SET); + default: + MainUtil.sendMessage(player, C.HOME_ARGUMENT); + return false; + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Setup.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Setup.java new file mode 100644 index 000000000..facea4952 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Setup.java @@ -0,0 +1,434 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.Configuration; +import com.github.intellectualsites.plotsquared.plot.config.ConfigurationNode; +import com.github.intellectualsites.plotsquared.plot.generator.GeneratorWrapper; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.SetupUtils; +import com.github.intellectualsites.plotsquared.plot.util.StringMan; +import com.github.intellectualsites.plotsquared.plot.util.WorldUtil; +import lombok.*; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.Map.Entry; + +@CommandDeclaration(command = "setup", permission = "plots.admin.command.setup", + description = "Setup wizard for plot worlds", usage = "/plot setup", aliases = {"create"}, + category = CommandCategory.ADMINISTRATION) public class Setup extends SubCommand { + + public void displayGenerators(PlotPlayer player) { + StringBuilder message = new StringBuilder(); + message.append("&6What generator do you want?"); + for (Entry> entry : SetupUtils.generators.entrySet()) { + if (entry.getKey().equals(PlotSquared.imp().getPluginName())) { + message.append("\n&8 - &2").append(entry.getKey()).append(" (Default Generator)"); + } else if (entry.getValue().isFull()) { + message.append("\n&8 - &7").append(entry.getKey()).append(" (Plot Generator)"); + } else { + message.append("\n&8 - &7").append(entry.getKey()).append(" (Unknown structure)"); + } + } + MainUtil.sendMessage(player, message.toString()); + } + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + // going through setup + SetupObject object = player.getMeta("setup"); + if (object == null) { + object = new SetupObject(); + player.setMeta("setup", object); + SetupUtils.manager.updateGenerators(); + sendMessage(player, C.SETUP_INIT); + displayGenerators(player); + return false; + } + if (args.length == 1) { + if (args[0].equalsIgnoreCase("cancel")) { + player.deleteMeta("setup"); + MainUtil.sendMessage(player, "&aCancelled setup"); + return false; + } + if (args[0].equalsIgnoreCase("back")) { + if (object.setup_index > 0) { + object.setup_index--; + ConfigurationNode node = object.step[object.setup_index]; + sendMessage(player, C.SETUP_STEP, object.setup_index + 1, node.getDescription(), + node.getType().getType(), String.valueOf(node.getDefaultValue())); + return false; + } else if (object.current > 0) { + object.current--; + } + } + } + int index = object.current; + switch (index) { + case 0: // choose generator + if (args.length != 1 || !SetupUtils.generators.containsKey(args[0])) { + String prefix = "\n&8 - &7"; + MainUtil.sendMessage(player, + "&cYou must choose a generator!" + prefix + StringMan + .join(SetupUtils.generators.keySet(), prefix) + .replaceAll(PlotSquared.imp().getPluginName(), + "&2" + PlotSquared.imp().getPluginName())); + sendMessage(player, C.SETUP_INIT); + return false; + } + object.setupGenerator = args[0]; + object.current++; + String partial = "\n&8 - &7PARTIAL&8 - &7Vanilla with clusters of plots"; + MainUtil.sendMessage(player, + "&6What world type do you want?\n&8 - &2DEFAULT&8 - &7Standard plot generation" + + "\n&8 - &7AUGMENTED&8 - &7Plot generation with terrain" + partial); + break; + case 1: // choose world type + List allTypes = Arrays.asList("default", "augmented", "partial"); + List allDesc = Arrays + .asList("Standard plot generation", "Plot generation with vanilla terrain", + "Vanilla with clusters of plots"); + ArrayList types = new ArrayList<>(); + if (SetupUtils.generators.get(object.setupGenerator).isFull()) { + types.add("default"); + } + types.add("augmented"); + types.add("partial"); + if (args.length != 1 || !types.contains(args[0].toLowerCase())) { + MainUtil.sendMessage(player, "&cYou must choose a world type!"); + for (String type : types) { + int i = allTypes.indexOf(type); + if (type.equals("default")) { + MainUtil + .sendMessage(player, "&8 - &2" + type + " &8-&7 " + allDesc.get(i)); + } else { + MainUtil + .sendMessage(player, "&8 - &7" + type + " &8-&7 " + allDesc.get(i)); + } + } + return false; + } + object.type = allTypes.indexOf(args[0].toLowerCase()); + GeneratorWrapper gen = SetupUtils.generators.get(object.setupGenerator); + if (object.type == 0) { + object.current = 6; + if (object.step == null) { + object.plotManager = object.setupGenerator; + object.step = + SetupUtils.generators.get(object.plotManager).getPlotGenerator() + .getNewPlotArea("CheckingPlotSquaredGenerator", null, null, null) + .getSettingNodes(); + SetupUtils.generators.get(object.plotManager).getPlotGenerator() + .processSetup(object); + } + if (object.step.length == 0) { + MainUtil.sendMessage(player, "&6What do you want your world to be called?"); + object.setup_index = 0; + return true; + } + ConfigurationNode step = object.step[object.setup_index]; + sendMessage(player, C.SETUP_STEP, object.setup_index + 1, step.getDescription(), + step.getType().getType(), String.valueOf(step.getDefaultValue())); + } else { + if (gen.isFull()) { + object.plotManager = object.setupGenerator; + object.setupGenerator = null; + object.step = + SetupUtils.generators.get(object.plotManager).getPlotGenerator() + .getNewPlotArea("CheckingPlotSquaredGenerator", null, null, null) + .getSettingNodes(); + SetupUtils.generators.get(object.plotManager).getPlotGenerator() + .processSetup(object); + } else { + object.plotManager = PlotSquared.imp().getPluginName(); + MainUtil.sendMessage(player, + "&c[WARNING] The specified generator does not identify as BukkitPlotGenerator"); + MainUtil.sendMessage(player, + "&7 - You may need to manually configure the other plugin"); + object.step = + SetupUtils.generators.get(object.plotManager).getPlotGenerator() + .getNewPlotArea("CheckingPlotSquaredGenerator", null, null, null) + .getSettingNodes(); + } + if (object.type == 2) { + MainUtil.sendMessage(player, "What would you like this area called?"); + object.current++; + } else { + MainUtil.sendMessage(player, "&6What terrain would you like in plots?" + + "\n&8 - &2NONE&8 - &7No terrain at all" + + "\n&8 - &7ORE&8 - &7Just some ore veins and trees" + + "\n&8 - &7ROAD&8 - &7Terrain separated by roads" + + "\n&8 - &7ALL&8 - &7Entirely vanilla generation"); + object.current = 5; + } + } + break; + case 2: // area id + if (!StringMan.isAlphanumericUnd(args[0])) { + MainUtil.sendMessage(player, "&cThe area id must be alphanumerical!"); + return false; + } + for (PlotArea area : PlotSquared.get().getPlotAreas()) { + if (area.id != null && area.id.equalsIgnoreCase(args[0])) { + MainUtil.sendMessage(player, + "&cYou must choose an area id that is not in use!"); + return false; + } + } + object.id = args[0]; + object.current++; + MainUtil.sendMessage(player, "&6What should be the minimum Plot Id?"); + break; + case 3: // min + object.min = PlotId.fromString(args[0]); + if (object.min == null) { + MainUtil.sendMessage(player, "&cYou must choose a valid minimum PlotId!"); + return false; + } + object.current++; + MainUtil.sendMessage(player, "&6What should be the maximum Plot Id?"); + break; + case 4: + // max + PlotId id = PlotId.fromString(args[0]); + if (id == null) { + MainUtil.sendMessage(player, "&cYou must choose a valid maximum PlotId!"); + return false; + } + if (id.x <= object.min.x || id.y <= object.min.y) { + MainUtil + .sendMessage(player, "&cThe max PlotId must be greater than the minimum!"); + return false; + } + object.max = id; + object.current++; + MainUtil.sendMessage(player, "&6What terrain would you like in plots?" + + "\n&8 - &2NONE&8 - &7No terrain at all" + + "\n&8 - &7ORE&8 - &7Just some ore veins and trees" + + "\n&8 - &7ROAD&8 - &7Terrain separated by roads" + + "\n&8 - &7ALL&8 - &7Entirely vanilla generation"); + break; + case 5: { // Choose terrain + List terrain = Arrays.asList("none", "ore", "road", "all"); + if (args.length != 1 || !terrain.contains(args[0].toLowerCase())) { + MainUtil.sendMessage(player, + "&cYou must choose the terrain!" + "\n&8 - &2NONE&8 - &7No terrain at all" + + "\n&8 - &7ORE&8 - &7Just some ore veins and trees" + + "\n&8 - &7ROAD&8 - &7Terrain separated by roads" + + "\n&8 - &7ALL&8 - &7Entirely vanilla generation"); + return false; + } + object.terrain = terrain.indexOf(args[0].toLowerCase()); + object.current++; + if (object.step == null) { + object.step = SetupUtils.generators.get(object.plotManager).getPlotGenerator() + .getNewPlotArea("CheckingPlotSquaredGenerator", null, null, null) + .getSettingNodes(); + } + ConfigurationNode step = object.step[object.setup_index]; + sendMessage(player, C.SETUP_STEP, object.setup_index + 1, step.getDescription(), + step.getType().getType(), String.valueOf(step.getDefaultValue())); + break; + } + case 6: // world setup + if (object.setup_index == object.step.length) { + MainUtil.sendMessage(player, "&6What do you want your world to be called?"); + object.setup_index = 0; + object.current++; + return true; + } + ConfigurationNode step = object.step[object.setup_index]; + if (args.length < 1) { + sendMessage(player, C.SETUP_STEP, object.setup_index + 1, step.getDescription(), + step.getType().getType(), String.valueOf(step.getDefaultValue())); + return false; + } + + boolean valid = false; + try { + valid = step.isValid(args[0]); + } catch (final Configuration.UnsafeBlockException e) { + C.NOT_ALLOWED_BLOCK.send(player, e.getUnsafeBlock().toString()); + } + if (valid) { + sendMessage(player, C.SETUP_VALID_ARG, step.getConstant(), args[0]); + step.setValue(args[0]); + object.setup_index++; + if (object.setup_index == object.step.length) { + onCommand(player, args); + return false; + } + step = object.step[object.setup_index]; + sendMessage(player, C.SETUP_STEP, object.setup_index + 1, step.getDescription(), + step.getType().getType(), String.valueOf(step.getDefaultValue())); + return false; + } else { + sendMessage(player, C.SETUP_INVALID_ARG, args[0], step.getConstant()); + sendMessage(player, C.SETUP_STEP, object.setup_index + 1, step.getDescription(), + step.getType().getType(), String.valueOf(step.getDefaultValue())); + return false; + } + case 7: + if (args.length != 1) { + MainUtil.sendMessage(player, "&cYou need to choose a world name!"); + return false; + } + if (WorldUtil.IMP.isWorld(args[0])) { + if (PlotSquared.get().hasPlotArea(args[0])) { + MainUtil.sendMessage(player, "&cThat world name is already taken!"); + return false; + } + MainUtil.sendMessage(player, + "&cThe world you specified already exists. After restarting, new terrain will use " + + PlotSquared.imp().getPluginName() + ", however you may need to " + + "reset the world for it to generate correctly!"); + } + object.world = args[0]; + player.deleteMeta("setup"); + String world; + if (object.setupManager == null) { + world = SetupUtils.manager.setupWorld(object); + } else { + world = object.setupManager.setupWorld(object); + } + try { + player.teleport(WorldUtil.IMP.getSpawn(world)); + } catch (Exception e) { + player.sendMessage("&cAn error occurred. See console for more information"); + e.printStackTrace(); + } + sendMessage(player, C.SETUP_FINISHED, object.world); + } + return false; + } + + private static final class StepPickGenerator extends SetupStep { + + @Getter private String generator; + + public StepPickGenerator() { + super("generator"); + } + + @Override public Collection showDescriptionMessage() { + SetupUtils.manager.updateGenerators(); + final List messages = new ArrayList<>(); + messages.add(new PlotMessage("What generator do you want?").color("$6")); + for (Entry> entry : SetupUtils.generators.entrySet()) { + final PlotMessage plotMessage = new PlotMessage(" - ").color("$8"); + if (entry.getKey().equals(PlotSquared.imp().getPluginName())) { + plotMessage.text(entry.getKey()).color("$8").tooltip("Select this generator") + .color("$2").command("/plot setup generator " + entry.getKey()) + .text(" (Default Generator)").color("$7"); + } else if (entry.getValue().isFull()) { + plotMessage.text(entry.getKey()).color("$8").tooltip("Select this generator") + .color("$7").command("/plot setup generator " + entry.getKey()) + .text(" (Plot Generator)").color("$7"); + } else { + plotMessage.text(entry.getKey()).color("$8").tooltip("Select this generator") + .color("$7").command("/plot setup generator " + entry.getKey()) + .text(" (Unknown Structure)").color("$7"); + } + messages.add(plotMessage); + } + return messages; + } + + @Override public boolean parseInut(String input) { + this.generator = input.toLowerCase(); + return true; + } + + @Nullable @Override public String getDefault() { + return null; + } + } + + + private static final class StepWorldType extends SetupStep { + + private static final Map WORLD_TYPES = new HashMap<>(); + + static { + WORLD_TYPES.put("default", "Standard plot generation"); + WORLD_TYPES.put("augmented", "Plot generation with vanilla terrain"); + WORLD_TYPES.put("partial", "Vanilla clusters of plots"); + } + + @Getter private String worldType; + + public StepWorldType() { + super("type"); + } + + @Override public Collection showDescriptionMessage() { + final List messages = new ArrayList<>(); + messages.add(new PlotMessage("What world type do you want?").color("$6")); + for (final Map.Entry worldType : WORLD_TYPES.entrySet()) { + messages.add(new PlotMessage(" - ").color("$8").text(worldType.getKey()) + .color(worldType.getKey().equals(getDefault()) ? "$2" : "$7") + .tooltip("Select this world type") + .command("/plot setup type " + worldType.getKey()) + .text(" (" + worldType.getValue() + ")").color("$7")); + } + return messages; + } + + @Override public boolean parseInut(String input) { + if (!WORLD_TYPES.keySet().contains(input.toLowerCase())) { + return false; + } + this.worldType = input.toLowerCase(); + return true; + } + + @Override public String getDefault() { + return "default"; + } + } + + + @ToString @EqualsAndHashCode(of = "uuid") @AllArgsConstructor + private static class SetupContext { + + private final UUID uuid; + + @Getter private String step; + + } + + + @RequiredArgsConstructor(access = AccessLevel.PROTECTED) + private abstract static class SetupStep { + + private final String stepName; + + public abstract Collection showDescriptionMessage(); + + public abstract boolean parseInut(String input); + + public final PlotMessage getUsage() { + return new PlotMessage("Usage: ").color("$1") + .text("/plot setup " + this.stepName + " ").color("$2").suggest( + "/plot setup " + this.stepName + (this.getDefault() != null ? + this.getDefault() : + "")); + } + + @Nullable public abstract String getDefault(); + + public void sendToPlayer(@NonNull final PlotPlayer plotPlayer) { + new PlotMessage("Setup Step: ").color("$6").text(this.stepName).color("$7") + .send(plotPlayer); + this.getUsage().send(plotPlayer); + this.showDescriptionMessage().forEach(plotMessage -> plotMessage.send(plotPlayer)); + if (this.getDefault() != null) { + new PlotMessage("Default: ").color("$6").text(this.getDefault()).color("$7"); + } + } + + } + +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SubCommand.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SubCommand.java new file mode 100644 index 000000000..396185e29 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/SubCommand.java @@ -0,0 +1,39 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Argument; +import com.github.intellectualsites.plotsquared.commands.Command; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal2; +import com.github.intellectualsites.plotsquared.plot.object.RunnableVal3; + +/** + * SubCommand class + * + * @Deprecated In favor of normal Command class + * @see Command(Command, boolean) + */ +public abstract class SubCommand extends Command { + public SubCommand() { + super(MainCommand.getInstance(), true); + } + + public SubCommand(Argument... arguments) { + this(); + setRequiredArguments(arguments); + } + + @Override public void execute(PlotPlayer player, String[] args, + RunnableVal3 confirm, + RunnableVal2 whenDone) { + onCommand(player, args); + } + + + public abstract boolean onCommand(PlotPlayer player, String[] args); + + public boolean sendMessage(PlotPlayer player, C message, Object... args) { + message.send(player, args); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Swap.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Swap.java new file mode 100644 index 000000000..80a96dfb6 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Swap.java @@ -0,0 +1,54 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.Permissions; + +@CommandDeclaration(usage = "/plot swap ", command = "swap", description = "Swap two plots", + aliases = {"switch"}, category = CommandCategory.CLAIMING, requiredType = RequiredType.NONE) +public class Swap extends SubCommand { + + @Override public boolean onCommand(final PlotPlayer player, String[] args) { + Location loc = player.getLocation(); + Plot plot1 = loc.getPlotAbs(); + if (plot1 == null) { + return !MainUtil.sendMessage(player, C.NOT_IN_PLOT); + } + if (!plot1.isOwner(player.getUUID()) && !Permissions + .hasPermission(player, C.PERMISSION_ADMIN.s())) { + MainUtil.sendMessage(player, C.NO_PLOT_PERMS); + return false; + } + if (args.length != 1) { + C.COMMAND_SYNTAX.send(player, getUsage()); + return false; + } + Plot plot2 = MainUtil.getPlotFromString(player, args[0], true); + if (plot2 == null) { + return false; + } + if (plot1.equals(plot2)) { + MainUtil.sendMessage(player, C.NOT_VALID_PLOT_ID); + MainUtil.sendMessage(player, C.COMMAND_SYNTAX, "/plot copy "); + return false; + } + if (!plot1.getArea().isCompatible(plot2.getArea())) { + C.PLOTWORLD_INCOMPATIBLE.send(player); + return false; + } + if (plot1.move(plot2, new Runnable() { + @Override public void run() { + MainUtil.sendMessage(player, C.SWAP_SUCCESS); + } + }, true)) { + return true; + } else { + MainUtil.sendMessage(player, C.SWAP_OVERLAP); + return false; + } + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Target.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Target.java new file mode 100644 index 000000000..03879463a --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Target.java @@ -0,0 +1,49 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.Argument; +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.object.Location; +import com.github.intellectualsites.plotsquared.plot.object.Plot; +import com.github.intellectualsites.plotsquared.plot.object.PlotPlayer; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.StringMan; + +@CommandDeclaration(command = "target", usage = "/plot target <|nearest>", + description = "Target a plot with your compass", permission = "plots.target", + requiredType = RequiredType.PLAYER, category = CommandCategory.INFO) public class Target + extends SubCommand { + + public Target() { + super(Argument.PlotID); + } + + @Override public boolean onCommand(PlotPlayer player, String[] args) { + Location location = player.getLocation(); + if (!location.isPlotArea()) { + MainUtil.sendMessage(player, C.NOT_IN_PLOT_WORLD); + return false; + } + Plot target = null; + if (StringMan.isEqualIgnoreCaseToAny(args[0], "near", "nearest")) { + int distance = Integer.MAX_VALUE; + for (Plot plot : PlotSquared.get().getPlots(location.getWorld())) { + double current = plot.getCenter().getEuclideanDistanceSquared(location); + if (current < distance) { + distance = (int) current; + target = plot; + } + } + if (target == null) { + MainUtil.sendMessage(player, C.FOUND_NO_PLOTS); + return false; + } + } else if ((target = MainUtil.getPlotFromString(player, args[0], true)) == null) { + return false; + } + player.setCompassTarget(target.getCenter()); + MainUtil.sendMessage(player, C.COMPASS_TARGET); + return true; + } +} diff --git a/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Template.java b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Template.java new file mode 100644 index 000000000..aed9c5ca6 --- /dev/null +++ b/Bukkit/src/main/java/com/github/intellectualsites/plotsquared/plot/commands/Template.java @@ -0,0 +1,201 @@ +package com.github.intellectualsites.plotsquared.plot.commands; + +import com.github.intellectualsites.plotsquared.commands.CommandDeclaration; +import com.github.intellectualsites.plotsquared.configuration.ConfigurationSection; +import com.github.intellectualsites.plotsquared.configuration.InvalidConfigurationException; +import com.github.intellectualsites.plotsquared.configuration.file.YamlConfiguration; +import com.github.intellectualsites.plotsquared.plot.PlotSquared; +import com.github.intellectualsites.plotsquared.plot.config.C; +import com.github.intellectualsites.plotsquared.plot.config.ConfigurationNode; +import com.github.intellectualsites.plotsquared.plot.config.Settings; +import com.github.intellectualsites.plotsquared.plot.object.*; +import com.github.intellectualsites.plotsquared.plot.util.MainUtil; +import com.github.intellectualsites.plotsquared.plot.util.SetupUtils; +import com.github.intellectualsites.plotsquared.plot.util.TaskManager; +import com.github.intellectualsites.plotsquared.plot.util.WorldUtil; +import com.github.intellectualsites.plotsquared.plot.util.block.GlobalBlockQueue; + +import java.io.*; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +@CommandDeclaration(command = "template", permission = "plots.admin", + description = "Create or use a world template", + usage = "/plot template [import|export]