diff --git a/bukkit/build.gradle b/bukkit/build.gradle index a7af4835..9e602560 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -53,7 +53,6 @@ apply plugin: 'com.github.johnrengelman.shadow' shadowJar { dependencies { include(dependency('com.github.luben:zstd-jni:1.1.1')) -// include(dependency('org.javassist:javassist:3.22.0-CR1')) include(dependency('co.aikar:fastutil-lite:1.0')) include(dependency(':core')) } diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java index 8aa5e82e..3361dc3a 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java @@ -627,6 +627,11 @@ public class FaweBukkit implements IFawe, Listener { return ((BlocksHubBukkit) blocksHubPlugin).getApi(); } + @Override + public boolean isMainThread() { + return Bukkit.isPrimaryThread(); + } + private Version version = null; public Version getVersion() { diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java index 370baa38..9f192985 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncChunk.java @@ -68,7 +68,7 @@ public class AsyncChunk implements Chunk { @Override public ChunkSnapshot getChunkSnapshot(boolean includeMaxblocky, boolean includeBiome, boolean includeBiomeTempRain) { - if (Thread.currentThread() == Fawe.get().getMainThread()) { + if (Fawe.isMainThread()) { return world.getChunkAt(x, z).getChunkSnapshot(includeMaxblocky, includeBiome, includeBiomeTempRain); } return whenLoaded(new RunnableVal() { @@ -80,7 +80,7 @@ public class AsyncChunk implements Chunk { } private T whenLoaded(RunnableVal task) { - if (Thread.currentThread() == Fawe.get().getMainThread()) { + if (Fawe.isMainThread()) { task.run(); return task.value; } diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java index 42e2a5d3..7f937735 100644 --- a/core/src/main/java/com/boydti/fawe/Fawe.java +++ b/core/src/main/java/com/boydti/fawe/Fawe.java @@ -201,6 +201,11 @@ public class Fawe { * @param s */ public static void debug(Object s) { + Actor actor = Request.request().getActor(); + if (actor != null && actor.isPlayer()) { + actor.print(BBC.color(BBC.PREFIX.original() + " " + s)); + return; + } debugPlain(BBC.PREFIX.original() + " " + s); } @@ -729,7 +734,7 @@ public class Fawe { } public static boolean isMainThread() { - return INSTANCE != null ? INSTANCE.thread == Thread.currentThread() : true; + return INSTANCE != null ? imp().isMainThread() : true; } /** diff --git a/core/src/main/java/com/boydti/fawe/IFawe.java b/core/src/main/java/com/boydti/fawe/IFawe.java index 5708ee91..626f3a7f 100644 --- a/core/src/main/java/com/boydti/fawe/IFawe.java +++ b/core/src/main/java/com/boydti/fawe/IFawe.java @@ -65,4 +65,8 @@ public interface IFawe { public default FormBuilder getFormBuilder() { return null; } + + default boolean isMainThread() { + return Fawe.get().getMainThread() == Thread.currentThread(); + } } diff --git a/core/src/main/java/com/boydti/fawe/command/FaweParser.java b/core/src/main/java/com/boydti/fawe/command/FaweParser.java index 9351e07f..30b30378 100644 --- a/core/src/main/java/com/boydti/fawe/command/FaweParser.java +++ b/core/src/main/java/com/boydti/fawe/command/FaweParser.java @@ -6,11 +6,8 @@ import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; import com.sk89q.worldedit.internal.registry.InputParser; import com.sk89q.worldedit.util.command.Dispatcher; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; + +import java.util.*; public abstract class FaweParser extends InputParser { protected FaweParser(WorldEdit worldEdit) { @@ -28,27 +25,13 @@ public abstract class FaweParser extends InputParser { public abstract Dispatcher getDispatcher(); - public List suggestRemaining(String input, String... expected) throws InputParseException { - List remainder = StringMan.split(input, ':'); - int len = remainder.size(); - if (len != expected.length - 1) { - if (len <= expected.length - 1 && len != 0) { - if (remainder.get(len - 1).endsWith(":")) { - throw new SuggestInputParseException(null, StringMan.join(expected, ":")); - } - throw new SuggestInputParseException(null, expected[0] + ":" + input + ":" + StringMan.join(Arrays.copyOfRange(expected, len + 1, 3), ":")); - } else { - throw new SuggestInputParseException(null, StringMan.join(expected, ":")); - } - } - return remainder; - } - protected static class ParseEntry { public boolean and; public String input; + public String full; - public ParseEntry(String input, boolean type) { + public ParseEntry(String full, String input, boolean type) { + this.full = full; this.input = input; this.and = type; } @@ -59,66 +42,54 @@ public abstract class FaweParser extends InputParser { } } - public List>> parse(String command) throws InputParseException { + public static List>> parse(String toParse) throws InputParseException { List>> keys = new ArrayList<>(); - List args = new ArrayList<>(); - int len = command.length(); - String current = null; - int end = -1; - boolean newEntry = true; - for (int i = 0; i < len; i++) { - int prefix = 0; - boolean or = false; - char c = command.charAt(i); - if (i < end) continue; + List inputs = new ArrayList<>(); + List and = new ArrayList<>(); + int last = 0; + outer: + for (int i = 0; i < toParse.length(); i++) { + char c = toParse.charAt(i); switch (c) { + case ',': case '&': - or = true; - case ',': { - prefix = 1; - if (current == null) { - throw new InputParseException("Duplicate separator"); + String result = toParse.substring(last, i); + if (!result.isEmpty()) { + inputs.add(result); + and.add(c == '&'); + } else { + throw new InputParseException("Invalid dangling character " + c); } - newEntry = true; - break; - } - case '[': { - int depth = 0; - end = len; - loop: - for (int j = i + 1; j < len; j++) { - char c2 = command.charAt(j); - switch (c2) { - case '[': - depth++; - continue; - case ']': - if (depth-- <= 0) { - end = j; - break loop; - } + last = i + 1; + continue outer; + default: + if (StringMan.getMatchingBracket(c) != c) { + int next = StringMan.findMatchingBracket(toParse, i); + if (next != -1) { + i = next; + } else { + toParse += "]"; + i = toParse.length(); } + continue outer; } - String arg = command.substring(i + 1, end); - args.add(arg); - break; - } - } - if (newEntry) { - newEntry = false; - int index = StringMan.indexOf(command, Math.max(i, end) + prefix, '[', '&', ','); - if (index < 0) index = len; - end = index; - current = command.substring(i + prefix, end); - if (prefix == 1) args = new ArrayList<>(); - ParseEntry entry = new ParseEntry(current, or); - keys.add(new AbstractMap.SimpleEntry<>(entry, args)); } } - for (int i = 0; i < keys.size() - 1; i++) { // Apply greedy and - if (keys.get(i + 1).getKey().and) { - keys.get(i).getKey().and = true; + inputs.add(toParse.substring(last, toParse.length())); + for (int i = 0; i < inputs.size(); i++) { + String full = inputs.get(i); + String command = full; + List args = new ArrayList<>(); + while (!command.isEmpty() && command.charAt(command.length() - 1) == ']') { + int startPos = StringMan.findMatchingBracket(command, command.length() - 1); + if (startPos == -1) break; + String arg = command.substring(startPos + 1, command.length() - 1); + args.add(arg); + command = full.substring(0, startPos); } + Collections.reverse(args); + ParseEntry entry = new ParseEntry(full, command, i > 0 ? and.get(i - 1) : false); + keys.add(new AbstractMap.SimpleEntry<>(entry, args)); } return keys; } diff --git a/core/src/main/java/com/boydti/fawe/command/SuggestInputParseException.java b/core/src/main/java/com/boydti/fawe/command/SuggestInputParseException.java index 98dc8ff6..0f62448b 100644 --- a/core/src/main/java/com/boydti/fawe/command/SuggestInputParseException.java +++ b/core/src/main/java/com/boydti/fawe/command/SuggestInputParseException.java @@ -2,87 +2,87 @@ package com.boydti.fawe.command; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.StringMan; +import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.extension.input.InputParseException; + +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.function.Supplier; + +import static com.google.common.base.Preconditions.checkNotNull; public class SuggestInputParseException extends InputParseException { - private final String message; + private final InputParseException cause; + private final SuggestSupplier> getSuggestions; private String prefix; - private ArrayList suggestions = new ArrayList<>(); - public SuggestInputParseException(String input, Collection inputs) { - super(""); - this.message = "Suggested input: " + StringMan.join(suggestions = getSuggestions(input, inputs), ", "); - this.prefix = ""; + public SuggestInputParseException(String msg, String prefix, SuggestSupplier> getSuggestions) { + this(new InputParseException(msg), prefix, getSuggestions); } - public SuggestInputParseException(String input, String... inputs) { - super(""); - this.message = "Suggested input: " + StringMan.join(suggestions = getSuggestions(input, inputs), ", "); - this.prefix = ""; + public static SuggestInputParseException of(Throwable other, String prefix, SuggestSupplier> getSuggestions) { + InputParseException e = find(other); + if (e != null) return of(e, prefix, getSuggestions); + return of(new InputParseException(other.getMessage()), prefix, getSuggestions); + } + + public static SuggestInputParseException of(InputParseException other, String prefix, SuggestSupplier> getSuggestions) { + if (other instanceof SuggestInputParseException) return (SuggestInputParseException) other; + return new SuggestInputParseException(other, prefix, getSuggestions); + } + + public SuggestInputParseException(InputParseException other, String prefix, SuggestSupplier> getSuggestions) { + super(other.getMessage()); + checkNotNull(getSuggestions); + checkNotNull(other); + this.cause = other; + this.getSuggestions = getSuggestions; + this.prefix = prefix; + } + + public interface SuggestSupplier { + T get() throws InputParseException; + } + + public static InputParseException find(Throwable e) { + do { + if (e instanceof InputParseException) return (InputParseException) e; + e = e.getCause(); + } + while (e != null); + return null; + } + + public static SuggestInputParseException get(Throwable e) { + Throwable t = e; + while (t.getCause() != null) { + t = t.getCause(); + if (t instanceof SuggestInputParseException) return (SuggestInputParseException) t; + } + return null; + } + + @Override + public synchronized Throwable getCause() { + return cause.getCause(); } @Override public String getMessage() { - return message; + return cause.getMessage(); } - public List getSuggestions() { - return MainUtil.prepend(prefix, suggestions); + + public List getSuggestions() throws InputParseException { + return getSuggestions.get(); } public SuggestInputParseException prepend(String input) { this.prefix = input + prefix; return this; } - - public static SuggestInputParseException get(Throwable e) { - if (e instanceof SuggestInputParseException) { - return (SuggestInputParseException) e; - } - Throwable cause = e.getCause(); - if (cause == null) { - return null; - } - return get(cause); - } - - private static ArrayList getSuggestions(String input, Collection inputs) { - ArrayList suggestions = new ArrayList<>(); - if (input != null) { - String tmp = input.toLowerCase(); - for (String s : inputs) { - if (s.startsWith(tmp)) { - suggestions.add(s); - } - - } - } - if (suggestions.isEmpty()) { - suggestions.addAll(inputs); - } - return suggestions; - } - - private static ArrayList getSuggestions(String input, String... inputs) { - ArrayList suggestions = new ArrayList<>(); - if (input != null) { - String tmp = input.toLowerCase(); - for (String s : inputs) { - if (s.startsWith(tmp)) { - suggestions.add(s); - } - - } - } - if (suggestions.isEmpty()) { - for (String s : inputs) { - suggestions.add(s); - } - } - return suggestions; - } } diff --git a/core/src/main/java/com/boydti/fawe/config/Commands.java b/core/src/main/java/com/boydti/fawe/config/Commands.java index c2187733..031c349b 100644 --- a/core/src/main/java/com/boydti/fawe/config/Commands.java +++ b/core/src/main/java/com/boydti/fawe/config/Commands.java @@ -30,6 +30,47 @@ public class Commands { } } + public static Command fromArgs(String[] aliases, String usage, String desc, int min, int max, String flags, String help, boolean queued /* ignored */) { + return new Command() { + @Override + public Class annotationType() { + return Command.class; + } + @Override + public String[] aliases() { + return aliases; + } + @Override + public String usage() { + return usage; + } + @Override + public String desc() { + return desc; + } + @Override + public int min() { + return min; + } + @Override + public int max() { + return max; + } + @Override + public String flags() { + return flags; + } + @Override + public String help() { + return help; + } + @Override + public boolean anyFlags() { + return !(flags.isEmpty() || flags.matches("[a-z]+")); + } + }; + } + public static Command translate(Class clazz, final Command command) { if (cmdConfig == null || command instanceof TranslatedCommand) { return command; diff --git a/core/src/main/java/com/boydti/fawe/config/Settings.java b/core/src/main/java/com/boydti/fawe/config/Settings.java index dd0d22eb..3bb9cb75 100644 --- a/core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/core/src/main/java/com/boydti/fawe/config/Settings.java @@ -76,8 +76,6 @@ public class Settings extends Config { @Comment({ "Put any minecraft or mod jars for FAWE to be aware of block textures", }) - public String PATTERNS = "patterns"; - public String MASKS = "masks"; public String TEXTURES = "textures"; public String HEIGHTMAP = "heightmap"; public String HISTORY = "history"; @@ -88,6 +86,7 @@ public class Settings extends Config { public String CLIPBOARD = "clipboard"; @Comment("Each player has their own sub directory for schematics") public boolean PER_PLAYER_SCHEMATICS = true; + public String COMMANDS = "commands"; } @Comment("Region restriction settings") diff --git a/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java b/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java index 9e324032..7ee97f1a 100644 --- a/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java +++ b/core/src/main/java/com/boydti/fawe/example/MappedFaweQueue.java @@ -21,10 +21,9 @@ import com.sk89q.worldedit.blocks.BlockMaterial; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.biome.BaseBiome; import com.sk89q.worldedit.world.registry.BundledBlockData; -import java.util.ArrayDeque; -import java.util.Collection; -import java.util.HashSet; -import java.util.UUID; + +import java.lang.reflect.InvocationTargetException; +import java.util.*; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; @@ -409,7 +408,7 @@ public abstract class MappedFaweQueue impl if (chunk != null) { return chunk; } - boolean sync = Thread.currentThread() == Fawe.get().getMainThread(); + boolean sync = Fawe.isMainThread(); if (sync) { return loadChunk(getWorld(), cx, cz, true); } else if (getSettings().HISTORY.CHUNK_WAIT_MS > 0) { diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/FaweChangeSet.java b/core/src/main/java/com/boydti/fawe/object/changeset/FaweChangeSet.java index a7b9b460..b1a79cd3 100644 --- a/core/src/main/java/com/boydti/fawe/object/changeset/FaweChangeSet.java +++ b/core/src/main/java/com/boydti/fawe/object/changeset/FaweChangeSet.java @@ -265,30 +265,34 @@ public abstract class FaweChangeSet implements ChangeSet { int bz = cz << 4; synchronized (FaweChangeSet.this) { // Biome changes - if (previous.getBiomeArray() != null) { - byte[] previousBiomes = previous.getBiomeArray(); + { byte[] nextBiomes = next.getBiomeArray(); - int index = 0; - for (int z = 0; z < 16; z++) { - int zz = bz + z; - for (int x = 0; x < 16; x++) { - byte idFrom = previousBiomes[index]; - byte idTo = nextBiomes[index]; - if (idFrom != idTo && idTo != 0) { - addBiomeChange(bx + x, zz, FaweCache.getBiome(idFrom & 0xFF), FaweCache.getBiome(idTo & 0xFF)); + if (nextBiomes != null) { + byte[] previousBiomes = previous.getBiomeArray(); + if (previousBiomes != null) { + + int index = 0; + for (int z = 0; z < 16; z++) { + int zz = bz + z; + for (int x = 0; x < 16; x++) { + byte idFrom = previousBiomes[index]; + byte idTo = nextBiomes[index]; + if (idFrom != idTo && idTo != 0) { + addBiomeChange(bx + x, zz, FaweCache.getBiome(idFrom & 0xFF), FaweCache.getBiome(idTo & 0xFF)); + } + index++; + } } - index++; } } - // TODO } // Block changes for (int layer = 0; layer < layers; layer++) { char[] currentLayer = next.getIdArray(layer); - char[] previousLayer = previous.getIdArray(layer); if (currentLayer == null) { continue; } + char[] previousLayer = previous.getIdArray(layer); int startY = layer << 4; int index = 0; for (int y = 0; y < 16; y++) { diff --git a/core/src/main/java/com/boydti/fawe/object/collection/SummedColorTable.java b/core/src/main/java/com/boydti/fawe/object/collection/SummedColorTable.java index 1bb13c7c..780aedf2 100644 --- a/core/src/main/java/com/boydti/fawe/object/collection/SummedColorTable.java +++ b/core/src/main/java/com/boydti/fawe/object/collection/SummedColorTable.java @@ -24,7 +24,7 @@ public class SummedColorTable { this.hasAlpha = new int[raw.length]; this.alpha = calculateAlpha ? new long[raw.length] : null; this.alphaInverse = calculateAlpha ? new float[256] : null; - this.areaInverses = new float[Character.MAX_VALUE]; + this.areaInverses = new float[1024 * 1024]; for (int i = 0; i < areaInverses.length; i++) { areaInverses[i] = 1f / (i + 1); } diff --git a/core/src/main/java/com/boydti/fawe/object/extent/NullExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/NullExtent.java index d594791c..52ebd9c6 100644 --- a/core/src/main/java/com/boydti/fawe/object/extent/NullExtent.java +++ b/core/src/main/java/com/boydti/fawe/object/extent/NullExtent.java @@ -14,6 +14,8 @@ import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.biome.BaseBiome; + +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -123,6 +125,12 @@ public class NullExtent extends FaweRegionExtent { return null; } + @Nullable + @Override + public Operation commit() { + return null; + } + @Override public int getNearestSurfaceLayer(int x, int z, int y, int minY, int maxY) { throw new FaweException(reason); diff --git a/core/src/main/java/com/boydti/fawe/object/extent/StripNBTExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/StripNBTExtent.java index 91f91365..1b858152 100644 --- a/core/src/main/java/com/boydti/fawe/object/extent/StripNBTExtent.java +++ b/core/src/main/java/com/boydti/fawe/object/extent/StripNBTExtent.java @@ -28,8 +28,7 @@ public class StripNBTExtent extends AbstractDelegateExtent { */ public StripNBTExtent(Extent extent, Set strip) { super(extent); - this. - strip = strip.toArray(new String[strip.size()]); + this.strip = strip.toArray(new String[strip.size()]); } @Override diff --git a/core/src/main/java/com/boydti/fawe/object/pattern/AngleColorPattern.java b/core/src/main/java/com/boydti/fawe/object/pattern/AngleColorPattern.java index b59a7cb4..e49ecd0a 100644 --- a/core/src/main/java/com/boydti/fawe/object/pattern/AngleColorPattern.java +++ b/core/src/main/java/com/boydti/fawe/object/pattern/AngleColorPattern.java @@ -48,7 +48,7 @@ public class AngleColorPattern extends DataAnglePattern { int height = extent.getNearestSurfaceTerrainBlock(x, z, y, 0, maxY); if (height > 0) { BaseBlock below = extent.getLazyBlock(x, height - 1, z); - if (FaweCache.canPassThrough(block.getId(), block.getData())) { + if (FaweCache.canPassThrough(below.getId(), below.getData())) { return Integer.MAX_VALUE; } } diff --git a/core/src/main/java/com/boydti/fawe/object/schematic/StructureFormat.java b/core/src/main/java/com/boydti/fawe/object/schematic/StructureFormat.java index 01d97d23..074b15b9 100644 --- a/core/src/main/java/com/boydti/fawe/object/schematic/StructureFormat.java +++ b/core/src/main/java/com/boydti/fawe/object/schematic/StructureFormat.java @@ -139,7 +139,7 @@ public class StructureFormat implements ClipboardReader, ClipboardWriter { Map entityEntryMap = entityEntry.getValue(); ListTag posTag = (ListTag) entityEntryMap.get("pos"); CompoundTag nbtTag = (CompoundTag) entityEntryMap.get("nbt"); - String id = ((StringTag) entityEntryMap.get("Id")).getValue(); + String id = nbtTag.getString("Id"); Location location = NBTConversions.toLocation(clipboard, posTag, nbtTag.getListTag("Rotation")); if (!id.isEmpty()) { BaseEntity state = new BaseEntity(id, nbtTag); diff --git a/core/src/main/java/com/boydti/fawe/util/MainUtil.java b/core/src/main/java/com/boydti/fawe/util/MainUtil.java index 5a5ca2f8..d955036f 100644 --- a/core/src/main/java/com/boydti/fawe/util/MainUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/MainUtil.java @@ -585,7 +585,7 @@ public class MainUtil { long ratio = total / compressedSize; long saved = total - compressedSize; - if (ratio > 3 && Thread.currentThread() != Fawe.get().getMainThread() && actor != null) { + if (ratio > 3 && !Fawe.isMainThread() && actor != null) { BBC.COMPRESSED.send(actor, saved, ratio); } } catch (Exception e) { diff --git a/core/src/main/java/com/boydti/fawe/util/StringMan.java b/core/src/main/java/com/boydti/fawe/util/StringMan.java index 873dbb37..b02de918 100644 --- a/core/src/main/java/com/boydti/fawe/util/StringMan.java +++ b/core/src/main/java/com/boydti/fawe/util/StringMan.java @@ -10,6 +10,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.function.Function; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; public class StringMan { public static String replaceFromMap(final String string, final Map replacements) { @@ -33,6 +35,85 @@ public class StringMan { return sb.toString(); } + public static boolean containsAny(CharSequence sequence, String any) { + for (int i = 0; i < sequence.length(); i++) { + if (any.indexOf(sequence.charAt(i)) != -1) return true; + } + return false; + } + + public static int findMatchingBracket(CharSequence sequence, int index) { + char startC = sequence.charAt(index); + char lookC = getMatchingBracket(startC); + if (lookC == startC) return -1; + boolean forward = isBracketForwards(startC); + int increment = forward ? 1 : -1; + int end = forward ? sequence.length() : -1; + int count = 0; + for (int i = index + increment; i != end; i += increment) { + char c = sequence.charAt(i); + if (c == startC) { + count++; + } else if (c == lookC && count-- == 0) { + return i; + } + } + return -1; + } + + public static String prettyFormat(double d) { + if (d == Double.MIN_VALUE) return "-∞"; + if (d == Double.MAX_VALUE) return "∞"; + if(d == (long) d) return String.format("%d",(long)d); + else return String.format("%s",d); + } + + public static boolean isBracketForwards(char c) { + switch (c) { + case '[': + case '(': + case '{': + case '<': + return true; + default: return false; + } + } + + public static char getMatchingBracket(char c) { + switch (c) { + case '[': return ']'; + case '(': return ')'; + case '{': return '}'; + case '<': return '>'; + case ']': return '['; + case ')': return '('; + case '}': return '{'; + case '>': return '<'; + default: return c; + } + } + + public static int parseInt(CharSequence string) { + int val = 0; + boolean neg = false; + int numIndex = 1; + int len = string.length(); + outer: + for (int i = len - 1; i >= 0; i--) { + char c = string.charAt(i); + switch (c) { + case '-': + val = -val; + break; + default: + val = val + (c - 48) * numIndex; + numIndex *= 10; + break; + } + } + return val; + } + public static String removeFromSet(final String string, final Collection replacements) { final StringBuilder sb = new StringBuilder(string); int size = string.length(); @@ -189,7 +270,7 @@ public class StringMan { return true; } - public static boolean isAlphanumericUnd(final String str) { + public static boolean isAlphanumericUnd(final CharSequence str) { for (int i = 0; i < str.length(); i++) { final char c = str.charAt(i); if ((c < 0x30) || ((c >= 0x3a) && (c <= 0x40)) || ((c > 0x5a) && (c <= 0x60)) || (c > 0x7a) || (c == '_')) { diff --git a/core/src/main/java/com/boydti/fawe/util/TaskManager.java b/core/src/main/java/com/boydti/fawe/util/TaskManager.java index 2663924e..c57e7a38 100644 --- a/core/src/main/java/com/boydti/fawe/util/TaskManager.java +++ b/core/src/main/java/com/boydti/fawe/util/TaskManager.java @@ -188,7 +188,7 @@ public abstract class TaskManager { if (r == null) { return; } - if (Thread.currentThread() == Fawe.get().getMainThread()) { + if (Fawe.isMainThread()) { r.run(); } else { task(r); @@ -336,7 +336,7 @@ public abstract class TaskManager { * @return */ public T syncWhenFree(final RunnableVal function, int timeout) { - if (Fawe.get().getMainThread() == Thread.currentThread()) { + if (Fawe.isMainThread()) { function.run(); return function.value; } @@ -389,7 +389,7 @@ public abstract class TaskManager { } public T sync(final Supplier function, int timeout) { - if (Fawe.get().getMainThread() == Thread.currentThread()) { + if (Fawe.isMainThread()) { return function.get(); } final AtomicBoolean running = new AtomicBoolean(true); diff --git a/core/src/main/java/com/boydti/fawe/util/image/ImageUtil.java b/core/src/main/java/com/boydti/fawe/util/image/ImageUtil.java index 89921a9f..98e77597 100644 --- a/core/src/main/java/com/boydti/fawe/util/image/ImageUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/image/ImageUtil.java @@ -3,6 +3,7 @@ package com.boydti.fawe.util.image; import com.boydti.fawe.Fawe; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.StringMan; import com.sk89q.worldedit.util.command.parametric.ParameterException; import java.awt.Graphics2D; import java.awt.RenderingHints; @@ -16,6 +17,13 @@ import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; public class ImageUtil { public static BufferedImage getScaledInstance(BufferedImage img, @@ -208,7 +216,6 @@ public class ImageUtil { } } - public static URI getImageURI(String arg) throws ParameterException { try { if (arg.startsWith("http")) { diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java index 146bd825..8ca32035 100644 --- a/core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -363,19 +363,6 @@ public class EditSession extends AbstractDelegateExtent implements HasFaweQueue, this(world, null, null, null, null, null, true, null, null, null, blockBag, eventBus, event); } - /** - * Lazily copy a region - * - * @param region - * @return - */ - public BlockArrayClipboard lazyCopy(Region region) { - WorldCopyClipboard faweClipboard = new WorldCopyClipboard(this, region); - BlockArrayClipboard weClipboard = new BlockArrayClipboard(region, faweClipboard); - weClipboard.setOrigin(region.getMinimumPoint()); - return weClipboard; - } - /** * The limit for this specific edit (blocks etc) * @@ -2865,9 +2852,7 @@ public class EditSession extends AbstractDelegateExtent implements HasFaweQueue, distribution.add(new Countable(FaweCache.CACHE_BLOCK[i], count)); } } - Collections.sort(distribution); // Collections.reverse(distribution); - return distribution; } diff --git a/core/src/main/java/com/sk89q/worldedit/LocalSession.java b/core/src/main/java/com/sk89q/worldedit/LocalSession.java index 9809e1a5..a8a21b2d 100644 --- a/core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -36,6 +36,7 @@ import com.boydti.fawe.object.extent.ResettableExtent; import com.boydti.fawe.util.*; import com.boydti.fawe.util.cui.CUI; import com.boydti.fawe.wrappers.WorldWrapper; +import com.intellectualcrafters.plot.object.PlotArea; import com.sk89q.jchronic.Chronic; import com.sk89q.jchronic.Options; import com.sk89q.jchronic.utils.Span; @@ -1170,7 +1171,7 @@ public class LocalSession implements TextureHolder { if (hasCUISupport) { actor.dispatchCUIEvent(event); - } else { + } else if (actor.isPlayer()) { CUI cui = Fawe.get().getCUI(actor); if (cui != null) cui.dispatchCUIEvent(event); } diff --git a/core/src/main/java/com/sk89q/worldedit/command/HelpBuilder.java b/core/src/main/java/com/sk89q/worldedit/command/HelpBuilder.java index 4e46d23f..11f87e90 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/HelpBuilder.java +++ b/core/src/main/java/com/sk89q/worldedit/command/HelpBuilder.java @@ -8,7 +8,8 @@ import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.platform.CommandManager; import com.sk89q.worldedit.util.command.*; -import com.sk89q.worldedit.util.command.parametric.ParametricCallable; +import com.sk89q.worldedit.util.command.parametric.AParametricCallable; + import java.util.*; public abstract class HelpBuilder implements Runnable { @@ -69,13 +70,12 @@ public abstract class HelpBuilder implements Runnable { if (c instanceof DelegateCallable) { c = ((DelegateCallable) c).getParent(); } - if (c instanceof ParametricCallable) { - Object obj = ((ParametricCallable) c).getObject(); - Command command = obj.getClass().getAnnotation(Command.class); + if (c instanceof AParametricCallable) { + Command command = ((AParametricCallable) c).getCommand(); if (command != null && command.aliases().length != 0) { group = command.aliases()[0]; } else { - group = obj.getClass().getSimpleName().replaceAll("Commands", "").replaceAll("Util$", ""); + group = ((AParametricCallable) c).getGroup(); } } else if (c instanceof Dispatcher) { group = mapping.getPrimaryAlias(); diff --git a/core/src/main/java/com/sk89q/worldedit/command/PatternCommands.java b/core/src/main/java/com/sk89q/worldedit/command/PatternCommands.java index 037c3bba..cc13f218 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/PatternCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/PatternCommands.java @@ -1,5 +1,8 @@ package com.sk89q.worldedit.command; +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweAPI; +import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.DataAnglePattern; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.clipboard.MultiClipboardHolder; @@ -8,11 +11,11 @@ import com.boydti.fawe.object.pattern.*; import com.boydti.fawe.object.random.SimplexRandom; import com.boydti.fawe.util.ColorUtil; import com.boydti.fawe.util.TextureUtil; +import com.boydti.fawe.wrappers.LocationMaskedPlayerWrapper; import com.sk89q.minecraft.util.commands.Command; -import com.sk89q.worldedit.EmptyClipboardException; -import com.sk89q.worldedit.LocalSession; -import com.sk89q.worldedit.Vector; -import com.sk89q.worldedit.WorldEdit; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.platform.Actor; @@ -26,11 +29,13 @@ import com.sk89q.worldedit.function.pattern.RandomPattern; import com.sk89q.worldedit.internal.expression.Expression; import com.sk89q.worldedit.internal.expression.ExpressionException; import com.sk89q.worldedit.regions.shape.WorldEditExpressionEnvironment; +import com.sk89q.worldedit.scripting.RhinoCraftScriptEngine; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.command.binding.Range; import com.sk89q.worldedit.util.command.parametric.Optional; import com.sk89q.worldedit.world.biome.BaseBiome; import java.awt.Color; +import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.List; diff --git a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index ca95ecfe..e793db68 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -45,6 +45,7 @@ import com.sk89q.worldedit.event.extent.PlayerSaveClipboardEvent; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.ClipboardFormats; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; import com.sk89q.worldedit.function.operation.Operations; @@ -54,6 +55,8 @@ import com.sk89q.worldedit.util.command.binding.Switch; import com.sk89q.worldedit.util.command.parametric.Optional; import com.sk89q.worldedit.util.io.file.FilenameException; import com.sk89q.worldedit.world.registry.WorldData; + +import javax.annotation.Nullable; import java.io.*; import java.net.URI; import java.net.URISyntaxException; @@ -69,6 +72,7 @@ import java.util.regex.Pattern; import static com.boydti.fawe.util.ReflectionUtils.as; +import static com.google.common.base.Preconditions.checkNotNull; /** * Commands that work with schematic files. @@ -187,16 +191,26 @@ public class SchematicCommands extends MethodCommands { player.print(BBC.getPrefix() + "Remapped schematic"); } + private File resolve(File dir, String filename, @Nullable ClipboardFormat format) { + if (format != null) { + if (!filename.matches(".*\\.[\\w].*")) { + filename = filename + "." + format.getExtension(); + } + return MainUtil.resolveRelative(new File(dir, filename)); + } + for (ClipboardFormat f : ClipboardFormat.values()) { + File file = MainUtil.resolveRelative(new File(dir, filename + "." + f.getExtension())); + if (file.exists()) return file; + } + return null; + } + @Command(aliases = {"load"}, usage = "[] ", desc = "Load a schematic into your clipboard") @Deprecated @CommandPermissions({"worldedit.clipboard.load", "worldedit.schematic.load", "worldedit.schematic.upload", "worldedit.schematic.load.other"}) - public void load(final Player player, final LocalSession session, @Optional("schematic") final String formatName, String filename) throws FilenameException { + public void load(final Player player, final LocalSession session, @Optional() final String formatName, String filename) throws FilenameException { final LocalConfiguration config = this.worldEdit.getConfiguration(); - final ClipboardFormat format = ClipboardFormat.findByAlias(formatName); - if (format == null) { - BBC.CLIPBOARD_INVALID_FORMAT.send(player, formatName); - return; - } + ClipboardFormat format = formatName == null ? null : ClipboardFormat.findByAlias(formatName); InputStream in = null; try { URI uri; @@ -220,8 +234,14 @@ public class SchematicCommands extends MethodCommands { File dir = Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS ? new File(working, player.getUniqueId().toString()) : working; File f; if (filename.startsWith("#")) { - f = player.openFileOpenDialog(new String[] { format.getExtension() }); - if (!f.exists()) { + String[] extensions; + if (format != null) { + extensions = new String[] { format.getExtension() }; + } else { + extensions = ClipboardFormats.getFileExtensionArray(); + } + f = player.openFileOpenDialog(extensions); + if (f == null || !f.exists()) { player.printError("Schematic " + filename + " does not exist! (" + f + ")"); return; } @@ -230,32 +250,30 @@ public class SchematicCommands extends MethodCommands { BBC.NO_PERM.send(player, "worldedit.schematic.load.other"); return; } - if (!filename.matches(".*\\.[\\w].*")) { - filename += "." + format.getExtension(); + if (format == null && filename.matches(".*\\.[\\w].*")) { + String extension = filename.substring(filename.lastIndexOf('.') + 1, filename.length()); + format = ClipboardFormat.findByExtension(extension); } - f = MainUtil.resolveRelative(new File(dir, filename)); + f = resolve(dir, filename, format); } - if (f.getName().replaceAll("." + format.getExtension(), "").isEmpty()) { - File directory = f.getParentFile(); - if (directory.exists()) { - int max = MainUtil.getMaxFileId(directory) - 1; - f = new File(directory, max + "." + format.getExtension()); - } else { - f = new File(directory, "1." + format.getExtension()); - } - } - if (!f.exists()) { + if (f == null || !f.exists()) { if (!filename.contains("../")) { dir = this.worldEdit.getWorkingDirectoryFile(config.saveDir); - f = this.worldEdit.getSafeSaveFile(player, dir, filename, format.getExtension(), format.getExtension()); + f = resolve(dir, filename, format); } } - if (!f.exists() || !MainUtil.isInSubDirectory(working, f)) { + if (f == null || !f.exists() || !MainUtil.isInSubDirectory(working, f)) { player.printError("Schematic " + filename + " does not exist! (" + f.exists() + "|" + f + "|" + (!MainUtil.isInSubDirectory(working, f)) + ")"); return; } + if (format == null) { + format = ClipboardFormat.findByFile(f); + if (format == null) { + BBC.CLIPBOARD_INVALID_FORMAT.send(player, f.getName()); + return; + } + } in = new FileInputStream(f); - uri = f.toURI(); } format.hold(player, uri, in); diff --git a/core/src/main/java/com/sk89q/worldedit/command/tool/AreaPickaxe.java b/core/src/main/java/com/sk89q/worldedit/command/tool/AreaPickaxe.java index 8828789f..0a87fb7d 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/tool/AreaPickaxe.java +++ b/core/src/main/java/com/sk89q/worldedit/command/tool/AreaPickaxe.java @@ -46,8 +46,8 @@ public class AreaPickaxe implements BlockTool { editSession.getSurvivalExtent().setToolUse(config.superPickaxeManyDrop); for (int x = ox - range; x <= ox + range; ++x) { - for (int y = oy - range; y <= oy + range; ++y) { - for (int z = oz - range; z <= oz + range; ++z) { + for (int z = oz - range; z <= oz + range; ++z) { + for (int y = oy + range; y >= oy - range; --y) { if (editSession.getLazyBlock(x, y, z).getId() != initialType) { continue; } diff --git a/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java b/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java index 8f7d5890..ffc1009e 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java @@ -1,8 +1,10 @@ package com.sk89q.worldedit.extension.factory; import com.boydti.fawe.command.FaweParser; +import com.boydti.fawe.command.SuggestInputParseException; import com.boydti.fawe.config.BBC; import com.boydti.fawe.util.StringMan; +import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandLocals; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.blocks.BaseBlock; @@ -22,11 +24,13 @@ import com.sk89q.worldedit.session.request.Request; import com.sk89q.worldedit.util.command.Dispatcher; import com.sk89q.worldedit.util.command.SimpleDispatcher; import com.sk89q.worldedit.util.command.parametric.ParametricBuilder; + import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class DefaultMaskParser extends FaweParser { private final Dispatcher dispatcher; @@ -52,23 +56,27 @@ public class DefaultMaskParser extends FaweParser { @Override public Mask parseFromInput(String input, ParserContext context) throws InputParseException { - if (input.isEmpty()) return null; + if (input.isEmpty()) { + throw new SuggestInputParseException("No input provided", "", () -> Stream.of("#", ",", "&").map(n -> n + ":").collect(Collectors.toList()) + // TODO namespaces + ); + } Extent extent = Request.request().getExtent(); if (extent == null) extent = context.getExtent(); - HashSet blocks = new HashSet(); - List intersection = new ArrayList<>(); - List union = new ArrayList<>(); + List> masks = new ArrayList<>(); + masks.add(new ArrayList<>()); + final CommandLocals locals = new CommandLocals(); Actor actor = context != null ? context.getActor() : null; if (actor != null) { locals.put(Actor.class, actor); } - // try { List>> parsed = parse(input); for (Map.Entry> entry : parsed) { ParseEntry pe = entry.getKey(); - String command = pe.input; + final String command = pe.input; + String full = pe.full; Mask mask = null; if (command.isEmpty()) { mask = parseFromInput(StringMan.join(entry.getValue(), ','), context); @@ -79,101 +87,116 @@ public class DefaultMaskParser extends FaweParser { if (charMask && input.charAt(0) == '=') { return parseFromInput(char0 + "[" + input.substring(1) + "]", context); } - if (mask == null) { - // Legacy syntax - if (charMask) { - switch (char0) { - case '\\': // - case '/': // - case '{': // - case '$': // - case '%': { - command = command.substring(1); - String value = command + ((entry.getValue().isEmpty()) ? "" : "[" + StringMan.join(entry.getValue(), "][") + "]"); - if (value.contains(":")) { - if (value.charAt(0) == ':') value.replaceFirst(":", ""); - value = value.replaceAll(":", "]["); - } - mask = parseFromInput(char0 + "[" + value + "]", context); - break; + if (char0 == '#') { + throw new SuggestInputParseException(new NoMatchException("Unkown mask: " + full + ", See: //masks"), full, + () -> { + if (full.length() == 1) return new ArrayList<>(dispatcher.getPrimaryAliases()); + return dispatcher.getAliases().stream().filter( + s -> s.startsWith(command.toLowerCase()) + ).collect(Collectors.toList()); } - case '|': - case '~': - case '<': - case '>': - case '!': - input = input.substring(input.indexOf(char0) + 1); - mask = parseFromInput(char0 + "[" + input + "]", context); - if (actor != null) { - BBC.COMMAND_CLARIFYING_BRACKET.send(actor, char0 + "[" + input + "]"); - } - return mask; + ); + } + // Legacy syntax + if (charMask) { + switch (char0) { + case '\\': // + case '/': // + case '{': // + case '$': // + case '%': { + String value = command.substring(1) + ((entry.getValue().isEmpty()) ? "" : "[" + StringMan.join(entry.getValue(), "][") + "]"); + if (value.contains(":")) { + if (value.charAt(0) == ':') value.replaceFirst(":", ""); + value = value.replaceAll(":", "]["); + } + mask = parseFromInput("#" + char0 + "[" + value + "]", context); + break; } + case '|': + case '~': + case '<': + case '>': + case '!': + input = input.substring(input.indexOf(char0) + 1); + mask = parseFromInput(char0 + "[" + input + "]", context); + if (actor != null) { + BBC.COMMAND_CLARIFYING_BRACKET.send(actor, char0 + "[" + input + "]"); + } + return mask; } - if (mask == null) { - if (command.startsWith("[")) { - int end = command.lastIndexOf(']'); - mask = parseFromInput(command.substring(1, end == -1 ? command.length() : end), context); - } else { - try { - context.setPreferringWildcard(true); - context.setRestricted(false); - BaseBlock block = worldEdit.getBlockFactory().parseFromInput(command, context); - if (pe.and) { - mask = new BlockMask(extent, block); - } else { - blocks.add(block); - continue; - } - } catch (NoMatchException e) { - throw new NoMatchException(e.getMessage() + " See: //masks"); - } - } + } + if (mask == null) { + if (command.startsWith("[")) { + int end = command.lastIndexOf(']'); + mask = parseFromInput(command.substring(1, end == -1 ? command.length() : end), context); + } else { + context.setPreferringWildcard(true); + context.setRestricted(false); + BaseBlock block = worldEdit.getBlockFactory().parseFromInput(command, context); + mask = new BlockMask(extent, block); } } } else { List args = entry.getValue(); - if (!args.isEmpty()) { - command += " " + StringMan.join(args, " "); + String cmdArgs = ((args.isEmpty()) ? "" : " " + StringMan.join(args, " ")); + try { + mask = (Mask) dispatcher.call(command + cmdArgs, locals, new String[0]); + } catch (Throwable e) { + throw SuggestInputParseException.of(e, full, () -> { + try { + List suggestions = dispatcher.get(command).getCallable().getSuggestions(cmdArgs, locals); + if (suggestions.size() <= 2) { + for (int i = 0; i < suggestions.size(); i++) { + String suggestion = suggestions.get(i); + if (suggestion.indexOf(' ') != 0) { + String[] split = suggestion.split(" "); + suggestion = BBC.color("[" + StringMan.join(split, "][") + "]"); + suggestions.set(i, suggestion); + } + } + } + return suggestions; + } catch (CommandException e1) { + throw new InputParseException(e1.getMessage()); + } catch (Throwable e2) { + e2.printStackTrace(); + throw new InputParseException(e2.getMessage()); + } + }); } - mask = (Mask) dispatcher.call(command, locals, new String[0]); } - if (pe.and) { // & - intersection.add(mask); - } else { - if (!intersection.isEmpty()) { - if (intersection.size() == 1) { - throw new InputParseException("Error, floating &"); - } - union.add(new MaskIntersection(intersection)); - intersection.clear(); - } - union.add(mask); + if (pe.and) { + masks.add(new ArrayList<>()); } + masks.get(masks.size() - 1).add(mask); } + } catch (InputParseException rethrow) { + throw rethrow; } catch (Throwable e) { + InputParseException ips = SuggestInputParseException.find(e); + if (ips != null) throw ips; + e.printStackTrace(); throw new InputParseException(e.getMessage(), e); } - if (!blocks.isEmpty()) { - union.add(new BlockMask(extent, blocks)); - } - if (!intersection.isEmpty()) { - if (intersection.size() == 1) { - throw new InputParseException("Error, floating &"); + List maskUnions = new ArrayList<>(); + for (List maskList : masks) { + if (maskList.size() == 1) { + maskUnions.add(maskList.get(0)); + } else if (maskList.size() != 0) { + maskUnions.add(new MaskUnion(maskList)); } - union.add(new MaskIntersection(intersection)); - intersection.clear(); } - if (union.isEmpty()) { - return null; - } else if (union.size() == 1) { - return union.get(0); + if (maskUnions.size() == 1) { + return maskUnions.get(0); + } else if (maskUnions.size() != 0) { + return new MaskIntersection(maskUnions); } else { - return new MaskUnion(union); + return null; } } - public static Class inject() { + public static Class inject() { return DefaultMaskParser.class; } } diff --git a/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java b/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java index 802389ab..6d55d23d 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java @@ -1,6 +1,8 @@ package com.sk89q.worldedit.extension.factory; import com.boydti.fawe.command.FaweParser; +import com.boydti.fawe.command.SuggestInputParseException; +import com.boydti.fawe.config.BBC; import com.boydti.fawe.object.random.TrueRandom; import com.boydti.fawe.util.StringMan; import com.sk89q.minecraft.util.commands.CommandException; @@ -16,13 +18,15 @@ import com.sk89q.worldedit.function.pattern.RandomPattern; import com.sk89q.worldedit.internal.command.ActorAuthorizer; import com.sk89q.worldedit.internal.command.WorldEditBinding; import com.sk89q.worldedit.internal.expression.Expression; -import com.sk89q.worldedit.internal.expression.ExpressionException; import com.sk89q.worldedit.util.command.Dispatcher; import com.sk89q.worldedit.util.command.SimpleDispatcher; import com.sk89q.worldedit.util.command.parametric.ParametricBuilder; + import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class HashTagPatternParser extends FaweParser { private final Dispatcher dispatcher; @@ -47,7 +51,10 @@ public class HashTagPatternParser extends FaweParser { @Override public Pattern parseFromInput(String input, ParserContext context) throws InputParseException { - if (input.isEmpty()) return null; + if (input.isEmpty()) { + throw new SuggestInputParseException("No input provided", "", () -> Stream.of("#", ",", "&").map(n -> n + ":").collect(Collectors.toList())); + // TODO namespace + } List chances = new ArrayList<>(); List patterns = new ArrayList<>(); final CommandLocals locals = new CommandLocals(); @@ -58,7 +65,8 @@ public class HashTagPatternParser extends FaweParser { try { for (Map.Entry> entry : parse(input)) { ParseEntry pe = entry.getKey(); - String command = pe.input; + final String command = pe.input; + String full = pe.full; Pattern pattern = null; double chance = 1; if (command.isEmpty()) { @@ -70,11 +78,22 @@ public class HashTagPatternParser extends FaweParser { if (charMask && input.charAt(0) == '=') { return parseFromInput(char0 + "[" + input.substring(1) + "]", context); } + if (char0 == '#') { + throw new SuggestInputParseException(new NoMatchException("Unkown pattern: " + full + ", See: //patterns"), full, + () -> { + if (full.length() == 1) return new ArrayList<>(dispatcher.getPrimaryAliases()); + return dispatcher.getAliases().stream().filter( + s -> s.startsWith(command.toLowerCase()) + ).collect(Collectors.toList()); + } + ); + } + + if (charMask) { switch (char0) { case '$': { - command = command.substring(1); - String value = command + ((entry.getValue().isEmpty()) ? "" : "[" + StringMan.join(entry.getValue(), "][") + "]"); + String value = command.substring(1) + ((entry.getValue().isEmpty()) ? "" : "[" + StringMan.join(entry.getValue(), "][") + "]"); if (value.contains(":")) { if (value.charAt(0) == ':') value.replaceFirst(":", ""); value = value.replaceAll(":", "]["); @@ -92,15 +111,15 @@ public class HashTagPatternParser extends FaweParser { int percentIndex = command.indexOf('%'); if (percentIndex != -1) { // Legacy percent pattern chance = Expression.compile(command.substring(0, percentIndex)).evaluate(); - command = command.substring(percentIndex + 1); + String value = command.substring(percentIndex + 1); if (!entry.getValue().isEmpty()) { - if (!command.isEmpty()) command += " "; - command += StringMan.join(entry.getValue(), " "); + if (!value.isEmpty()) value += " "; + value += StringMan.join(entry.getValue(), " "); } - pattern = parseFromInput(command, context); + pattern = parseFromInput(value, context); } else { // legacy block pattern try { - pattern = worldEdit.getBlockFactory().parseFromInput(command, context); + pattern = worldEdit.getBlockFactory().parseFromInput(pe.full, context); } catch (NoMatchException e) { throw new NoMatchException(e.getMessage() + " See: //patterns"); } @@ -109,18 +128,45 @@ public class HashTagPatternParser extends FaweParser { } } else { List args = entry.getValue(); - if (!args.isEmpty()) { - command += " " + StringMan.join(args, " "); + String cmdArgs = ((args.isEmpty()) ? "" : " " + StringMan.join(args, " ")); + try { + pattern = (Pattern) dispatcher.call(command + cmdArgs, locals, new String[0]); + } catch (Throwable e) { + throw SuggestInputParseException.of(e, full, () -> { + try { + List suggestions = dispatcher.get(command).getCallable().getSuggestions(cmdArgs, locals); + if (suggestions.size() <= 2) { + for (int i = 0; i < suggestions.size(); i++) { + String suggestion = suggestions.get(i); + if (suggestion.indexOf(' ') != 0) { + String[] split = suggestion.split(" "); + suggestion = BBC.color("[" + StringMan.join(split, "][") + "]"); + suggestions.set(i, suggestion); + } + } + } + return suggestions; + } catch (CommandException e1) { + throw new InputParseException(e1.getMessage()); + } catch (Throwable e2) { + e2.printStackTrace(); + throw new InputParseException(e2.getMessage()); + } + }); } - pattern = (Pattern) dispatcher.call(command, locals, new String[0]); } if (pattern != null) { patterns.add(pattern); chances.add(chance); } } - } catch (CommandException | ExpressionException e) { - throw new RuntimeException(e); + } catch (InputParseException rethrow) { + throw rethrow; + } catch (Throwable e) { + InputParseException ips = SuggestInputParseException.find(e); + if (ips != null) throw ips; + e.printStackTrace(); + throw new InputParseException(e.getMessage(), e); } if (patterns.isEmpty()) { return null; @@ -135,7 +181,8 @@ public class HashTagPatternParser extends FaweParser { } } - public static Class inject() { + + public static Class inject() { return HashTagPatternParser.class; } } diff --git a/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java b/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java index 1573377a..56d6096d 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java @@ -48,17 +48,14 @@ import com.sk89q.worldedit.event.platform.CommandSuggestionEvent; import com.sk89q.worldedit.function.factory.Deform; import com.sk89q.worldedit.function.factory.Deform.Mode; import com.sk89q.worldedit.internal.command.*; +import com.sk89q.worldedit.scripting.CommandScriptLoader; import com.sk89q.worldedit.session.request.Request; import com.sk89q.worldedit.util.auth.AuthorizationException; -import com.sk89q.worldedit.util.command.CommandCallable; -import com.sk89q.worldedit.util.command.Dispatcher; -import com.sk89q.worldedit.util.command.InvalidUsageException; +import com.sk89q.worldedit.util.command.*; import com.sk89q.worldedit.util.command.composition.ProvidedValue; import com.sk89q.worldedit.util.command.fluent.CommandGraph; import com.sk89q.worldedit.util.command.fluent.DispatcherNode; -import com.sk89q.worldedit.util.command.parametric.ExceptionConverter; -import com.sk89q.worldedit.util.command.parametric.LegacyCommandsHandler; -import com.sk89q.worldedit.util.command.parametric.ParametricBuilder; +import com.sk89q.worldedit.util.command.parametric.*; import com.sk89q.worldedit.util.eventbus.Subscribe; import com.sk89q.worldedit.util.logging.DynamicStreamHandler; import com.sk89q.worldedit.util.logging.LogFormat; @@ -184,13 +181,13 @@ public final class CommandManager { * @param clazz The class containing all the sub command methods * @param aliases The aliases to give the command */ - public void registerCommands(Object clazz, Object processor, String... aliases) { + public void registerCommands(Object clazz, CallableProcessor processor, String... aliases) { if (platform != null) { if (aliases.length == 0) { - builder.registerMethodsAsCommands(dispatcher, clazz); + builder.registerMethodsAsCommands(dispatcher, clazz, processor); } else { DispatcherNode graph = new CommandGraph().builder(builder).commands(); - graph = graph.registerMethods(clazz); + graph = graph.registerMethods(clazz, processor); dispatcher.registerCommand(graph.graph().getDispatcher(), aliases); } platform.registerCommands(dispatcher); @@ -244,6 +241,9 @@ public final class CommandManager { } } + commandMap.clear(); + methodMap.clear(); + dispatcher = graph .group("/anvil") .describeAs("Anvil command") @@ -295,6 +295,13 @@ public final class CommandManager { public void register(Platform platform) { log.log(Level.FINE, "Registering commands with " + platform.getClass().getCanonicalName()); + this.platform = null; + + try { + new CommandScriptLoader().load(); + } catch (Throwable e) { + e.printStackTrace(); + } LocalConfiguration config = platform.getConfiguration(); boolean logging = config.logCommands; @@ -403,6 +410,7 @@ public final class CommandManager { // exceptions without writing a hook into every dispatcher, we need to unwrap these // exceptions and rethrow their converted form, if their is one. try { + Request.request().setActor(finalActor); Object result = dispatcher.call(Joiner.on(" ").join(split), locals, new String[0]); } catch (Throwable t) { // Use the exception converter to convert the exception if any of its causes @@ -485,6 +493,16 @@ public final class CommandManager { TaskManager.IMP.taskNow(new Runnable() { @Override public void run() { +// int space0 = args.indexOf(' '); +// String arg0 = space0 == -1 ? args : args.substring(0, space0); +// CommandMapping cmd = dispatcher.get(arg0); +// if (cmd != null && cmd.getCallable() instanceof AParametricCallable) { +// Command info = ((AParametricCallable) cmd.getCallable()).getDefinition(); +// if (!info.queued()) { +// handleCommandOnCurrentThread(finalEvent); +// return; +// } +// } if (!fp.runAction(new Runnable() { @Override public void run() { @@ -522,7 +540,8 @@ public final class CommandManager { return commandLog; } - public static Class inject() { + + public static Class inject() { return CommandManager.class; } } \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java b/core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java index d9e9e9de..a3ed80d9 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/platform/PlayerProxy.java @@ -1,6 +1,8 @@ package com.sk89q.worldedit.extension.platform; +import com.boydti.fawe.util.ReflectionUtils; import com.google.common.base.Preconditions; +import com.sk89q.util.ReflectionUtil; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.WorldVector; @@ -13,7 +15,13 @@ import com.sk89q.worldedit.internal.cui.CUIEvent; import com.sk89q.worldedit.session.SessionKey; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.World; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; import javax.annotation.Nullable; public class PlayerProxy extends AbstractPlayerActor { diff --git a/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java b/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java index c9be9b79..72a44188 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java @@ -213,9 +213,7 @@ public class AbstractDelegateExtent implements LightingExtent { } @Override - public final - @Nullable - Operation commit() { + public @Nullable Operation commit() { Operation ours = commitBefore(); Operation other = null; if (extent != this) other = extent.commit(); diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/ClipboardFormats.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/ClipboardFormats.java new file mode 100644 index 00000000..e11e2471 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/ClipboardFormats.java @@ -0,0 +1,97 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.extent.clipboard; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; + +import javax.annotation.Nullable; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class ClipboardFormats { + /** + * Find the clipboard format named by the given alias. + * + * @param alias + * the alias + * @return the format, otherwise null if none is matched + */ + @Nullable + public static ClipboardFormat findByAlias(String alias) { + return ClipboardFormat.findByAlias(alias); + } + + /** + * Detect the format of given a file. + * + * @param file + * the file + * @return the format, otherwise null if one cannot be detected + */ + @Nullable + public static ClipboardFormat findByFile(File file) { + checkNotNull(file); + + for (ClipboardFormat format : ClipboardFormat.values()) { + if (format.isFormat(file)) { + return format; + } + } + + return null; + } + + /** + * @return a multimap from a file extension to the potential matching formats. + */ + public static Multimap getFileExtensionMap() { + HashMultimap map = HashMultimap.create(); + for (ClipboardFormat format : ClipboardFormat.values()) { + map.put(format.getExtension(), format); + } + return map; + } + + public static Collection getAll() { + return Arrays.asList(ClipboardFormat.values()); + } + + /** + * Not public API, only used by SchematicCommands. + * It is not in SchematicCommands because it may rely on internal register calls. + */ + public static String[] getFileExtensionArray() { + List exts = new ArrayList<>(); + HashMultimap map = HashMultimap.create(); + for (ClipboardFormat format : ClipboardFormat.values()) { + exts.add(format.getExtension()); + } + return exts.toArray(new String[exts.size()]); + } + + private ClipboardFormats() {} +} \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java index b4896385..f4c314cb 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java @@ -45,6 +45,7 @@ import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.event.extent.PlayerSaveClipboardEvent; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; @@ -540,6 +541,18 @@ public enum ClipboardFormat { return aliasMap.get(alias.toLowerCase().trim()); } + @Nullable + public static ClipboardFormat findByExtension(String extension) { + checkNotNull(extension); + extension = extension.toLowerCase(); + for (ClipboardFormat format : values()) { + if (format.getExtension().equalsIgnoreCase(extension)) { + return format; + } + } + return null; + } + /** * Detect the format given a file. * diff --git a/core/src/main/java/com/sk89q/worldedit/scripting/CommandScriptLoader.java b/core/src/main/java/com/sk89q/worldedit/scripting/CommandScriptLoader.java new file mode 100644 index 00000000..1c865a9b --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/scripting/CommandScriptLoader.java @@ -0,0 +1,102 @@ +package com.sk89q.worldedit.scripting; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweAPI; +import com.boydti.fawe.command.FaweParser; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.util.MainUtil; +import com.google.common.base.Charsets; +import com.google.common.io.CharStreams; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.BrushProcessor; +import com.sk89q.worldedit.extension.factory.DefaultMaskParser; +import com.sk89q.worldedit.extension.factory.HashTagPatternParser; +import com.sk89q.worldedit.extension.platform.CommandManager; +import com.sk89q.worldedit.util.command.ProcessedCallable; +import com.sk89q.worldedit.util.command.parametric.FunctionParametricCallable; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class CommandScriptLoader { + private final NashornCraftScriptEngine engine; + private final String loader; + + public CommandScriptLoader() throws IOException { + this.engine = new NashornCraftScriptEngine(); + + try (InputStream inputStream = Fawe.class.getResourceAsStream("/cs_adv.js")) { + this.loader = CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8)); + } + } + + /** + * Load all file commands + * @throws Throwable + */ + public void load() throws Throwable { + File commands = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.COMMANDS); + if (commands.exists()) { + for (File file : commands.listFiles()) add(new String[0], file); + } + } + + private void add(String[] aliases, File file) throws Throwable { + if (file.isDirectory()) { + if (aliases.length == 0) { + String[] newAliases = new String[] {file.getName()}; + for (File newFile : file.listFiles()) { + add(newAliases, newFile); + } + } else { + Fawe.debug("Ignoring nested directory: " + file); + } + } else { + String name = file.getName(); + if (name.endsWith(".js")) { + Fawe.debug("Loading script: " + name); + List cmds = getCommands(file, Collections.emptyMap()); + FaweParser parser = null; + if (aliases.length == 1) { + switch (aliases[0]) { + case "brush": + if (!cmds.isEmpty()) { + BrushProcessor processor = new BrushProcessor(WorldEdit.getInstance()); + for (FunctionParametricCallable cmd : cmds) { + ProcessedCallable processed = new ProcessedCallable(cmd, processor); + CommandManager.getInstance().registerCommand(aliases, cmd.getCommand(), processed); + } + } + return; + case "patterns": + parser = FaweAPI.getParser(HashTagPatternParser.class); + break; + case "masks": + parser = FaweAPI.getParser(DefaultMaskParser.class); + break; + } + if (parser != null) { + for (FunctionParametricCallable cmd : cmds) { + parser.getDispatcher().registerCommand(cmd, cmd.getCommand().aliases()); + } + return; + } + } + for (FunctionParametricCallable cmd : cmds) { + CommandManager.getInstance().registerCommand(aliases, cmd.getCommand(), cmd); + } + } + } + } + + private List getCommands(File file, Map vars) throws Throwable { + String script = new String(Files.readAllBytes(file.toPath())) + loader; + return (List) engine.evaluate(script, file.getPath(), vars); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/worldedit/scripting/NashornCraftScriptEngine.java b/core/src/main/java/com/sk89q/worldedit/scripting/NashornCraftScriptEngine.java new file mode 100644 index 00000000..02a377f2 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/scripting/NashornCraftScriptEngine.java @@ -0,0 +1,79 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.scripting; + +import com.boydti.fawe.Fawe; +import com.sk89q.worldedit.WorldEditException; +import jdk.nashorn.api.scripting.NashornScriptEngineFactory; + +import javax.script.ScriptEngine; +import javax.script.ScriptException; +import javax.script.SimpleBindings; +import java.util.Map; + +public class NashornCraftScriptEngine implements CraftScriptEngine { + private static NashornScriptEngineFactory FACTORY; + private int timeLimit; + + @Override + public void setTimeLimit(int milliseconds) { + timeLimit = milliseconds; + } + + @Override + public int getTimeLimit() { + return timeLimit; + } + + @Override + public Object evaluate(String script, String filename, Map args) throws Throwable { + ClassLoader cl = Fawe.get().getClass().getClassLoader(); + Thread.currentThread().setContextClassLoader(cl); + synchronized (NashornCraftScriptEngine.class) { + if (FACTORY == null) FACTORY = new NashornScriptEngineFactory(); + } + ; + ScriptEngine engine = FACTORY.getScriptEngine("--language=es6"); + SimpleBindings bindings = new SimpleBindings(); + + for (Map.Entry entry : args.entrySet()) { + bindings.put(entry.getKey(), entry.getValue()); + } + + try { + Object result = engine.eval(script, bindings); + return result; + } catch (Error e) { + e.printStackTrace(); + throw new ScriptException(e.getMessage()); + } catch (Throwable e) { + e.printStackTrace(); + while (e.getCause() != null) { + e = e.getCause(); + } + if (e instanceof WorldEditException) { + throw e; + } + throw e; + } finally { + } + } + +} diff --git a/core/src/main/java/com/sk89q/worldedit/session/SessionManager.java b/core/src/main/java/com/sk89q/worldedit/session/SessionManager.java index 7dc3aae5..7851c6bf 100644 --- a/core/src/main/java/com/sk89q/worldedit/session/SessionManager.java +++ b/core/src/main/java/com/sk89q/worldedit/session/SessionManager.java @@ -169,7 +169,6 @@ public class SessionManager { checkNotNull(owner); LocalSession session = getIfPresent(owner); - LocalConfiguration config = worldEdit.getConfiguration(); SessionKey sessionKey = owner.getSessionKey(); // No session exists yet -- create one @@ -182,12 +181,14 @@ public class SessionManager { session = new LocalSession(); } + LocalConfiguration config = worldEdit.getConfiguration(); session.setConfiguration(config); session.setBlockChangeLimit(config.defaultChangeLimit); sessions.put(getKey(owner), new SessionHolder(sessionKey, session)); } + LocalConfiguration config = worldEdit.getConfiguration(); // Set the limit on the number of blocks that an operation can // change at once, or don't if the owner has an override or there // is no limit. There is also a default limit diff --git a/core/src/main/java/com/sk89q/worldedit/session/request/Request.java b/core/src/main/java/com/sk89q/worldedit/session/request/Request.java index 55d9c006..cb0ff309 100644 --- a/core/src/main/java/com/sk89q/worldedit/session/request/Request.java +++ b/core/src/main/java/com/sk89q/worldedit/session/request/Request.java @@ -21,7 +21,10 @@ package com.sk89q.worldedit.session.request; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.util.command.parametric.ParametricCallable; import com.sk89q.worldedit.world.World; import javax.annotation.Nullable; @@ -30,19 +33,16 @@ import javax.annotation.Nullable; */ public final class Request { - private static final ThreadLocal threadLocal = - new ThreadLocal() { - @Override - protected Request initialValue() { - return new Request(); - } - }; + private static final ThreadLocal threadLocal = ThreadLocal.withInitial(Request::new); private @Nullable World world; private @Nullable + Actor actor; + private + @Nullable LocalSession session; private @Nullable @@ -87,6 +87,15 @@ public final class Request { return null; } + @Nullable + public Actor getActor() { + return actor; + } + + public void setActor(@Nullable Actor actor) { + this.actor = actor; + } + /** * Get the request session. * @@ -143,7 +152,8 @@ public final class Request { threadLocal.remove(); } - public static Class inject() { + + public static Class inject() { return Request.class; } } diff --git a/core/src/main/java/com/sk89q/worldedit/util/command/SimpleDispatcher.java b/core/src/main/java/com/sk89q/worldedit/util/command/SimpleDispatcher.java index 80e68ad5..cc923c63 100644 --- a/core/src/main/java/com/sk89q/worldedit/util/command/SimpleDispatcher.java +++ b/core/src/main/java/com/sk89q/worldedit/util/command/SimpleDispatcher.java @@ -72,6 +72,7 @@ public class SimpleDispatcher implements Dispatcher { continue; } else { Fawe.debug("Replacing commands is currently undefined behavior: " + StringMan.getString(alias)); + commands.put(lower, mapping); continue; } } diff --git a/core/src/main/java/com/sk89q/worldedit/util/command/parametric/AParametricCallable.java b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/AParametricCallable.java new file mode 100644 index 00000000..aa23bc5c --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/AParametricCallable.java @@ -0,0 +1,310 @@ +package com.sk89q.worldedit.util.command.parametric; + +import com.boydti.fawe.command.SuggestInputParseException; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.util.chat.UsageMessage; +import com.sk89q.minecraft.util.commands.*; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.util.command.*; + +import java.lang.reflect.InvocationTargetException; +import java.util.*; + +public abstract class AParametricCallable implements CommandCallable { +// private final ParametricBuilder builder; +// private ParameterData[] parameters; +// private Set valueFlags = new HashSet(); +// private boolean anyFlags; +// private Set legacyFlags = new HashSet(); +// private SimpleDescription description = new SimpleDescription(); +// private String permission; +// private Command command; + + public abstract ParameterData[] getParameters(); + public abstract Set getValueFlags(); + public abstract Set getLegacyFlags(); + public abstract SimpleDescription getDescription(); + public abstract String[] getPermissions(); + public abstract ParametricBuilder getBuilder(); + public abstract boolean anyFlags(); + public abstract Command getCommand(); + public Command getDefinition() { + return getCommand(); + } + public abstract String getGroup(); + @Override + public abstract String toString(); + + /** + * Get the right {@link ArgumentStack}. + * + * @param parameter the parameter + * @param existing the existing scoped context + * @return the context to use + */ + public static ArgumentStack getScopedContext(Parameter parameter, ArgumentStack existing) { + if (parameter.getFlag() != null) { + CommandContext context = existing.getContext(); + + if (parameter.isValueFlag()) { + return new StringArgumentStack(context, context.getFlag(parameter.getFlag()), false); + } else { + String v = context.hasFlag(parameter.getFlag()) ? "true" : "false"; + return new StringArgumentStack(context, v, true); + } + } + + return existing; + } + + /** + * Get whether a parameter is allowed to consume arguments. + * + * @param i the index of the parameter + * @param scoped the scoped context + * @return true if arguments may be consumed + */ + public boolean mayConsumeArguments(int i, ContextArgumentStack scoped) { + CommandContext context = scoped.getContext(); + ParameterData parameter = getParameters()[i]; + + // Flag parameters: Always consume + // Required non-flag parameters: Always consume + // Optional non-flag parameters: + // - Before required parameters: Consume if there are 'left over' args + // - At the end: Always consumes + + if (parameter.isOptional()) { + if (parameter.getFlag() != null) { + return !parameter.isValueFlag() || context.hasFlag(parameter.getFlag()); + } else { + int numberFree = context.argsLength() - scoped.position(); + for (int j = i; j < getParameters().length; j++) { + if (getParameters()[j].isNonFlagConsumer() && !getParameters()[j].isOptional()) { + // We already checked if the consumed count was > -1 + // when we created this object + numberFree -= getParameters()[j].getConsumedCount(); + } + } + + // Skip this optional parameter + if (numberFree < 1) { + return false; + } + } + } + + return true; + } + + /** + * Get the default value for a parameter. + * + * @param i the index of the parameter + * @param scoped the scoped context + * @return a value + * @throws ParameterException on an error + * @throws CommandException on an error + */ + public Object getDefaultValue(int i, ContextArgumentStack scoped) throws ParameterException, CommandException, InvocationTargetException { + CommandContext context = scoped.getContext(); + ParameterData parameter = getParameters()[i]; + + String[] defaultValue = parameter.getDefaultValue(); + if (defaultValue != null) { + try { + return parameter.getBinding().bind(parameter, new StringArgumentStack(context, defaultValue, false), false); + } catch (MissingParameterException e) { + throw new ParametricException( + "The default value of the parameter using the binding " + + parameter.getBinding().getClass() + " in the method\n" + + toString() + "\nis invalid"); + } + } + + return null; + } + + + /** + * Check to see if all arguments, including flag arguments, were consumed. + * + * @param scoped the argument scope + * @throws UnconsumedParameterException thrown if parameters were not consumed + */ + public void checkUnconsumed(ContextArgumentStack scoped) throws UnconsumedParameterException { + CommandContext context = scoped.getContext(); + String unconsumed; + String unconsumedFlags = getUnusedFlags(context); + + if ((unconsumed = scoped.getUnconsumed()) != null) { + throw new UnconsumedParameterException(unconsumed + " " + unconsumedFlags); + } + + if (unconsumedFlags != null) { + throw new UnconsumedParameterException(unconsumedFlags); + } + } + + /** + * Get any unused flag arguments. + * + * @param context the command context + */ + public String getUnusedFlags(CommandContext context) { + if (!anyFlags()) { + Set unusedFlags = null; + for (char flag : context.getFlags()) { + boolean found = false; + + if (getLegacyFlags().contains(flag)) { + break; + } + + for (ParameterData parameter : getParameters()) { + Character paramFlag = parameter.getFlag(); + if (paramFlag != null && flag == paramFlag) { + found = true; + break; + } + } + + if (!found) { + if (unusedFlags == null) { + unusedFlags = new HashSet(); + } + unusedFlags.add(flag); + } + } + + if (unusedFlags != null) { + StringBuilder builder = new StringBuilder(); + for (Character flag : unusedFlags) { + builder.append("-").append(flag).append(" "); + } + + return builder.toString().trim(); + } + } + + return null; + } + + @Override + public boolean testPermission(CommandLocals locals) { + String[] perms = getPermissions(); + if (perms != null && perms.length != 0) { + for (String perm : perms) { + if (getBuilder().getAuthorizer().testPermission(locals, perm)) { + return true; + } + } + + return false; + } else { + return true; + } + } + + @Override + public List getSuggestions(String arguments, CommandLocals locals) throws CommandException { + String[] split = ("ignored" + " " + arguments).split(" ", -1); + + // &a &f + // &cerrors + + CommandContext context = new CommandContext(split, getValueFlags(), !arguments.endsWith(" "), locals); + ContextArgumentStack scoped = new ContextArgumentStack(context); + SuggestionContext suggestable = context.getSuggestionContext(); + + List suggestions = new ArrayList<>(2); + ParameterData parameter = null; + ParameterData[] parameters = getParameters(); + String consumed = ""; + + boolean hasSuggestion = false; + int maxConsumedI = 0; // The maximum argument index + int minConsumedI = 0; // The minimum argument that has been consumed + // Collect parameters + try { + for (;maxConsumedI < parameters.length; maxConsumedI++) { + parameter = parameters[maxConsumedI]; + if (parameter.getBinding().getBehavior(parameter) != BindingBehavior.PROVIDES) { + // Parse the user input into a method argument + ArgumentStack usedArguments = getScopedContext(parameter, scoped); + + usedArguments.mark(); + try { + parameter.getBinding().bind(parameter, usedArguments, false); + minConsumedI = maxConsumedI + 1; + } catch (Throwable e) { + while (e.getCause() != null && !(e instanceof ParameterException || e instanceof InvocationTargetException)) e = e.getCause(); + consumed = usedArguments.reset(); + // Not optional? Then we can't execute this command + if (!parameter.isOptional()) { + if (!(e instanceof MissingParameterException)) minConsumedI = maxConsumedI; + throw e; + } + } + } + } + if (minConsumedI >= maxConsumedI && (parameter == null || parameter.getType() == CommandContext.class)) checkUnconsumed(scoped); + } catch (MissingParameterException ignore) { + } catch (UnconsumedParameterException e) { + suggestions.add(BBC.color("&cToo many parameters! Unused parameters: " + e.getUnconsumed())); + } catch (ParameterException e) { + String name = parameter.getName(); + suggestions.add(BBC.color("&cFor parameter '" + name + "': " + e.getMessage())); + } catch (InvocationTargetException e) { + Throwable tmp = e; + SuggestInputParseException suggestion = SuggestInputParseException.get(tmp); + try { + if (suggestion != null && !suggestion.getSuggestions().isEmpty()) { + hasSuggestion = true; + suggestions.addAll(suggestion.getSuggestions()); + } else { + Throwable t = tmp; + while (t.getCause() != null) t = t.getCause(); + String msg = t.getMessage(); + String name = parameter.getName(); + if (msg != null && !msg.isEmpty()) + suggestions.add(BBC.color("&cFor parameter '" + name + "': " + msg)); + } + } catch (InputParseException e2) { + throw new WrappedCommandException(e2); + } + } catch (Throwable t) { + t.printStackTrace(); + throw new WrappedCommandException(t); + } + // If there's 1 or less suggestions already, then show parameter suggestion + if (!hasSuggestion && suggestions.size() <= 1) { + StringBuilder suggestion = new StringBuilder(); + outer: + for (String prefix = ""; minConsumedI < parameters.length; minConsumedI++) { + parameter = parameters[minConsumedI]; + if (parameter.getBinding().getBehavior(parameter) != BindingBehavior.PROVIDES) { + suggestion.append(prefix); + List argSuggestions = parameter.getBinding().getSuggestions(parameter, consumed); + switch (argSuggestions.size()) { + case 0: + break; + case 1: + suggestion.append(argSuggestions.iterator().next()); + break; + default: + suggestion.setLength(0); + suggestions.addAll(argSuggestions); + break outer; + + } + consumed = ""; + prefix = " "; + } + } + if (suggestion.length() != 0) suggestions.add(suggestion.toString()); + } + return suggestions; + } +} diff --git a/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ContextArgumentStack.java b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ContextArgumentStack.java index 5c32ed02..14581b9c 100644 --- a/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ContextArgumentStack.java +++ b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ContextArgumentStack.java @@ -139,7 +139,7 @@ public class ContextArgumentStack implements ArgumentStack { */ @Override public String reset() { - String value = context.getString(markedIndex, index - 1); + String value = (index - 1 > markedIndex) ? context.getString(markedIndex, index - 1) : ""; index = markedIndex; return value; } diff --git a/core/src/main/java/com/sk89q/worldedit/util/command/parametric/FunctionParametricCallable.java b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/FunctionParametricCallable.java new file mode 100644 index 00000000..af055b3d --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/FunctionParametricCallable.java @@ -0,0 +1,328 @@ +package com.sk89q.worldedit.util.command.parametric; + +import com.boydti.fawe.util.StringMan; +import com.google.common.primitives.Chars; +import com.sk89q.minecraft.util.commands.*; +import com.sk89q.worldedit.util.command.*; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; +import java.util.*; +import java.util.function.Function; + +public class FunctionParametricCallable extends AParametricCallable { + + private final ParametricBuilder builder; + private final ParameterData[] parameters; + private final Set valueFlags = new HashSet(); + private final boolean anyFlags; + private final Set legacyFlags = new HashSet(); + private final SimpleDescription description = new SimpleDescription(); + private final String permission; + private final Command command; + private final Function function; + private final String group; + + public FunctionParametricCallable(ParametricBuilder builder, String group, Command command, String permission, List arguments, Function function) { + this.command = command; + this.permission = permission; + this.builder = builder; + this.function = function; + this.group = group; + + List paramParsables = new ArrayList<>(); + { + Map bindings = builder.getBindings(); + Map unqualified = new HashMap<>(); + for (Map.Entry entry : bindings.entrySet()) { + Type type = entry.getKey(); + String typeStr = type.getTypeName(); + unqualified.put(typeStr, type); + unqualified.put(typeStr.substring(typeStr.lastIndexOf('.') + 1), type); + } + { + Object[] param = null; // name | type | optional value + boolean checkEq = false; + int checkEqI = 0; + for (int i = 0; i < arguments.size(); i++) { + String arg = arguments.get(i); + if (arg.equals("=")) { + checkEqI++; + checkEq = true; + } else if (param == null || !checkEq) { + if (param != null) paramParsables.add(param); + param = new Object[3]; + param[0] = arg; + if (arg.length() == 1 && command.flags().contains(arg)) { + param[1] = Boolean.class; + } else { + param[1] = String.class; + } + param[2] = null; + checkEqI = 0; + checkEq = false; + } else { + if (checkEqI == 1) { + param[1] = unqualified.getOrDefault(arg, String.class); + checkEq = false; + } + else if (checkEqI == 2) { + char c = arg.charAt(0); + if (c == '\'' || c == '"') arg = arg.substring(1, arg.length() - 1); + param[2] = arg; + checkEqI = 0; + checkEq = false; + } + } + } + if (param != null) paramParsables.add(param); + } + } + + parameters = new ParameterData[paramParsables.size()]; + List userParameters = new ArrayList(); + + // This helps keep tracks of @Nullables that appear in the middle of a list + // of parameters + int numOptional = 0; +// + // Go through each parameter + for (int i = 0; i < paramParsables.size(); i++) { + Object[] parsable = paramParsables.get(i); + String paramName = (String) parsable[0]; + Type type = (Type) parsable[1]; + String optional = (String) parsable[2]; + + ParameterData parameter = new ParameterData(); + parameter.setType(type); + parameter.setModifiers(new Annotation[0]); + + boolean flag = paramName.length() == 1 && command.flags().contains(paramName); + if (flag) { + parameter.setFlag(paramName.charAt(0), type != boolean.class && type != Boolean.class); + } + + if (optional != null) { + parameter.setOptional(true); + if (!optional.equalsIgnoreCase("null")) parameter.setDefaultValue(new String[]{optional}); + } + + parameter.setName(paramName); + + // Track all value flags + if (parameter.isValueFlag()) { + valueFlags.add(parameter.getFlag()); + } + + // No special @annotation binding... let's check for the type + if (parameter.getBinding() == null) { + parameter.setBinding(builder.getBindings().get(type)); + + // Don't know how to parse for this type of value + if (parameter.getBinding() == null) { + throw new ParametricException("Don't know how to handle the parameter type '" + type + "' in\n" + StringMan.getString(command.aliases())); + } + } + + // Do some validation of this parameter + parameter.validate(() -> StringMan.getString(command.aliases()), i + 1); + + // Keep track of optional parameters + if (parameter.isOptional() && parameter.getFlag() == null) { + numOptional++; + } else { + if (numOptional > 0 && parameter.isNonFlagConsumer()) { + if (parameter.getConsumedCount() < 0) { + throw new ParametricException( + "Found an parameter using the binding " + + parameter.getBinding().getClass().getCanonicalName() + + "\nthat does not know how many arguments it consumes, but " + + "it follows an optional parameter\nMethod: " + StringMan.getString(command.aliases())); + } + } + } + + parameters[i] = parameter; + + // Make a list of "real" parameters + if (parameter.isUserInput()) { + userParameters.add(parameter); + } + } + + + // Gather legacy flags + anyFlags = command.anyFlags(); + legacyFlags.addAll(Chars.asList(command.flags().toCharArray())); + + // Finish description + description.setDescription(!command.desc().isEmpty() ? command.desc() : null); + description.setHelp(!command.help().isEmpty() ? command.help() : null); + description.overrideUsage(!command.usage().isEmpty() ? command.usage() : null); + description.setPermissions(Arrays.asList(permission)); + + if (command.usage().isEmpty() && (command.min() > 0 || command.max() > 0)) { + boolean hasUserParameters = false; + + for (ParameterData parameter : parameters) { + if (parameter.getBinding().getBehavior(parameter) != BindingBehavior.PROVIDES) { + hasUserParameters = true; + break; + } + } + + if (!hasUserParameters) { + description.overrideUsage("(unknown usage information)"); + } + } + + // Set parameters + description.setParameters(userParameters); + } + + @Override + public String getGroup() { + return group; + } + + @Override + public Command getCommand() { + return command; + } + + @Override + public ParameterData[] getParameters() { + return parameters; + } + + public Set getValueFlags() { + return valueFlags; + } + + @Override + public Set getLegacyFlags() { + return legacyFlags; + } + + @Override + public Object call(String stringArguments, CommandLocals locals, String[] parentCommands) throws CommandException { + // Test permission + if (!testPermission(locals)) { + throw new CommandPermissionsException(); + } + if (locals.get(CommandCallable.class) == null) locals.put(CommandCallable.class, this); + + String calledCommand = parentCommands.length > 0 ? parentCommands[parentCommands.length - 1] : "_"; + String[] split = (calledCommand + " " + stringArguments).split(" ", -1); + CommandContext context = new CommandContext(split, getValueFlags(), false, locals); + + // Provide help if -? is specified + if (context.hasFlag('?')) { + throw new InvalidUsageException(null, this, true); + } + + Object[] args = new Object[parameters.length]; + ContextArgumentStack arguments = new ContextArgumentStack(context); + ParameterData parameter = null; + + try { + // preProcess handlers + { + + } + + // Collect parameters + for (int i = 0; i < parameters.length; i++) { + parameter = parameters[i]; + + if (mayConsumeArguments(i, arguments)) { + // Parse the user input into a method argument + ArgumentStack usedArguments = getScopedContext(parameter, arguments); + + try { + usedArguments.mark(); + args[i] = parameter.getBinding().bind(parameter, usedArguments, false); + } catch (ParameterException e) { + // Not optional? Then we can't execute this command + if (!parameter.isOptional()) { + throw e; + } + + usedArguments.reset(); + args[i] = getDefaultValue(i, arguments); + } + } else { + args[i] = getDefaultValue(i, arguments); + } + } + + // Check for unused arguments + checkUnconsumed(arguments); + + // preInvoke handlers + { + if (context.argsLength() < command.min()) { + throw new MissingParameterException(); + } + if (command.max() != -1 && context.argsLength() > command.max()) { + throw new UnconsumedParameterException(context.getRemainingString(command.max())); + } + } + + // Execute! + Object result = function.apply(args); + + // postInvoke handlers + { + } + return result; + } catch (MissingParameterException e) { + throw new InvalidUsageException("Too few parameters!", this, true); + } catch (UnconsumedParameterException e) { + throw new InvalidUsageException("Too many parameters! Unused parameters: " + e.getUnconsumed(), this, true); + } catch (ParameterException e) { + assert parameter != null; + String name = parameter.getName(); + + throw new InvalidUsageException("For parameter '" + name + "': " + e.getMessage(), this, true); + } catch (InvocationTargetException e) { + if (e.getCause() instanceof CommandException) { + throw (CommandException) e.getCause(); + } + throw new WrappedCommandException(e); + } catch (Throwable t) { + throw new WrappedCommandException(t); + } + } + + @Override + public boolean testPermission(CommandLocals locals) { + return permission != null ? (builder.getAuthorizer().testPermission(locals, permission)) : true; + } + + @Override + public SimpleDescription getDescription() { + return description; + } + + @Override + public String[] getPermissions() { + return new String[]{permission}; + } + + @Override + public ParametricBuilder getBuilder() { + return builder; + } + + @Override + public boolean anyFlags() { + return anyFlags; + } + + @Override + public String toString() { + return command.aliases()[0]; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParameterData.java b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParameterData.java index 322a7669..764f6f99 100644 --- a/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParameterData.java +++ b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParameterData.java @@ -23,9 +23,13 @@ import com.sk89q.worldedit.util.command.SimpleParameter; import com.sk89q.worldedit.util.command.binding.PrimitiveBindings; import com.sk89q.worldedit.util.command.binding.Range; import com.sk89q.worldedit.util.command.binding.Text; + +import javax.xml.ws.Provider; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.util.concurrent.Callable; +import java.util.function.Supplier; /** * Describes a parameter in detail. @@ -155,6 +159,10 @@ public class ParameterData extends SimpleParameter { * Validate this parameter and its binding. */ public void validate(Method method, int parameterIndex) throws ParametricException { + validate(() -> method.toGenericString(), parameterIndex); + } + + public void validate(Supplier method, int parameterIndex) throws ParametricException { // We can't have indeterminate consumers without @Switches otherwise // it may screw up parameter processing for later bindings BindingBehavior behavior = getBinding().getBehavior(this); @@ -166,7 +174,7 @@ public class ParameterData extends SimpleParameter { getBinding().getClass().getCanonicalName() + "\nmay or may not consume parameters (isIndeterminateConsumer(" + type + ") = true)" + "\nand therefore @Switch(flag) is required for parameter #" + parameterIndex + " of \n" + - method.toGenericString()); + method.get()); } // getConsumedCount() better return -1 if the BindingBehavior is not CONSUMES @@ -176,7 +184,7 @@ public class ParameterData extends SimpleParameter { getBinding().getClass().getCanonicalName() + "\neven though its behavior type is " + behavior.name() + "\nfor parameter #" + parameterIndex + " of \n" + - method.toGenericString()); + method.get()); } // getConsumedCount() should not return 0 if the BindingBehavior is not PROVIDES @@ -186,7 +194,7 @@ public class ParameterData extends SimpleParameter { getBinding().getClass().getCanonicalName() + "\nwhen its behavior type is " + behavior.name() + " and not PROVIDES " + "\nfor parameter #" + parameterIndex + " of \n" + - method.toGenericString()); + method.get()); } } diff --git a/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java index 873ccfa2..cc68e24e 100644 --- a/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java +++ b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/ParametricCallable.java @@ -49,7 +49,7 @@ import java.util.Set; /** * The implementation of a {@link CommandCallable} for the {@link ParametricBuilder}. */ -public class ParametricCallable implements CommandCallable { +public class ParametricCallable extends AParametricCallable { private final ParametricBuilder builder; private final Object object; @@ -60,6 +60,7 @@ public class ParametricCallable implements CommandCallable { private final Set legacyFlags = new HashSet(); private final SimpleDescription description = new SimpleDescription(); private final CommandPermissions commandPermissions; + private final Command definition; /** * Create a new instance. @@ -179,6 +180,22 @@ public class ParametricCallable implements CommandCallable { // Get permissions annotation commandPermissions = method.getAnnotation(CommandPermissions.class); + this.definition = definition; + } + + @Override + public Command getCommand() { + return object.getClass().getAnnotation(Command.class); + } + + @Override + public Command getDefinition() { + return definition; + } + + @Override + public String getGroup() { + return object.getClass().getSimpleName().replaceAll("Commands", "").replaceAll("Util$", ""); } @Override @@ -187,6 +204,7 @@ public class ParametricCallable implements CommandCallable { if (!testPermission(locals)) { throw new CommandPermissionsException(); } + if (locals.get(CommandCallable.class) == null) locals.put(CommandCallable.class, this); String calledCommand = parentCommands.length > 0 ? parentCommands[parentCommands.length - 1] : "_"; String[] split = (calledCommand + " " + stringArguments).split(" ", -1); @@ -279,94 +297,8 @@ public class ParametricCallable implements CommandCallable { } @Override - public List getSuggestions(String arguments, CommandLocals locals) throws CommandException { - String[] split = ("ignored" + " " + arguments).split(" ", -1); - CommandContext context = new CommandContext(split, getValueFlags(), !arguments.endsWith(" "), locals); - ContextArgumentStack scoped = new ContextArgumentStack(context); - SuggestionContext suggestable = context.getSuggestionContext(); - - - // For /command -f | - // For /command -f flag| - if (suggestable.forFlag()) { - for (int i = 0; i < parameters.length; i++) { - ParameterData parameter = parameters[i]; - - if (parameter.getFlag() == suggestable.getFlag()) { - String prefix = context.getFlag(parameter.getFlag()); - if (prefix == null) { - prefix = ""; - } - - return parameter.getBinding().getSuggestions(parameter, prefix); - } - } - - // This should not happen - return new ArrayList(); - } - - int consumerIndex = 0; - ParameterData lastConsumer = null; - String lastConsumed = null; - - for (int i = 0; i < parameters.length; i++) { - ParameterData parameter = parameters[i]; - if (parameter.getFlag() != null) { - continue; // We already handled flags - } - - try { - scoped.mark(); - parameter.getBinding().bind(parameter, scoped, true); - if (scoped.wasConsumed()) { - lastConsumer = parameter; - lastConsumed = context.getString(scoped.position() - 1); - consumerIndex++; - } - } catch (MissingParameterException e) { - // For /command value1 |value2 - // For /command |value1 value2 - if (suggestable.forHangingValue()) { - return parameter.getBinding().getSuggestions(parameter, ""); - } else { - // For /command value1| value2 - if (lastConsumer != null) { - return lastConsumer.getBinding().getSuggestions(lastConsumer, lastConsumed); - // For /command| value1 value2 - // This should never occur - } else { - throw new RuntimeException("Invalid suggestion context"); - } - } - } catch (ParameterException | InvocationTargetException e) { - SuggestInputParseException suggestion = SuggestInputParseException.get(e); - if (suggestion != null) { - return suggestion.getSuggestions(); - } - if (suggestable.forHangingValue()) { - String name = getDescription().getParameters().get(consumerIndex).getName(); - throw new InvalidUsageException("For parameter '" + name + "': " + e.getMessage(), this); - } else { - return parameter.getBinding().getSuggestions(parameter, ""); - } - } - } - // For /command value1 value2 | - if (suggestable.forHangingValue()) { - // There's nothing that we can suggest because there's no more parameters - // to add on, and we can't change the previous parameter - return new ArrayList(); - } else { - // For /command value1 value2| - if (lastConsumer != null) { - return lastConsumer.getBinding().getSuggestions(lastConsumer, lastConsumed); - // This should never occur - } else { - throw new RuntimeException("Invalid suggestion context"); - } - - } + public ParameterData[] getParameters() { + return parameters; } /** @@ -378,179 +310,34 @@ public class ParametricCallable implements CommandCallable { return valueFlags; } + @Override + public Set getLegacyFlags() { + return legacyFlags; + } + @Override public SimpleDescription getDescription() { return description; } @Override - public boolean testPermission(CommandLocals locals) { - if (commandPermissions != null) { - for (String perm : commandPermissions.value()) { - if (builder.getAuthorizer().testPermission(locals, perm)) { - return true; - } - } - - return false; - } else { - return true; - } + public String[] getPermissions() { + return commandPermissions != null ? commandPermissions.value() : new String[0]; } - /** - * Get the right {@link ArgumentStack}. - * - * @param parameter the parameter - * @param existing the existing scoped context - * @return the context to use - */ - public static ArgumentStack getScopedContext(Parameter parameter, ArgumentStack existing) { - if (parameter.getFlag() != null) { - CommandContext context = existing.getContext(); - - if (parameter.isValueFlag()) { - return new StringArgumentStack(context, context.getFlag(parameter.getFlag()), false); - } else { - String v = context.hasFlag(parameter.getFlag()) ? "true" : "false"; - return new StringArgumentStack(context, v, true); - } - } - - return existing; + @Override + public ParametricBuilder getBuilder() { + return builder; } - /** - * Get whether a parameter is allowed to consume arguments. - * - * @param i the index of the parameter - * @param scoped the scoped context - * @return true if arguments may be consumed - */ - public boolean mayConsumeArguments(int i, ContextArgumentStack scoped) { - CommandContext context = scoped.getContext(); - ParameterData parameter = parameters[i]; - - // Flag parameters: Always consume - // Required non-flag parameters: Always consume - // Optional non-flag parameters: - // - Before required parameters: Consume if there are 'left over' args - // - At the end: Always consumes - - if (parameter.isOptional()) { - if (parameter.getFlag() != null) { - return !parameter.isValueFlag() || context.hasFlag(parameter.getFlag()); - } else { - int numberFree = context.argsLength() - scoped.position(); - for (int j = i; j < parameters.length; j++) { - if (parameters[j].isNonFlagConsumer() && !parameters[j].isOptional()) { - // We already checked if the consumed count was > -1 - // when we created this object - numberFree -= parameters[j].getConsumedCount(); - } - } - - // Skip this optional parameter - if (numberFree < 1) { - return false; - } - } - } - - return true; + @Override + public boolean anyFlags() { + return anyFlags; } - /** - * Get the default value for a parameter. - * - * @param i the index of the parameter - * @param scoped the scoped context - * @return a value - * @throws ParameterException on an error - * @throws CommandException on an error - */ - public Object getDefaultValue(int i, ContextArgumentStack scoped) throws ParameterException, CommandException, InvocationTargetException { - CommandContext context = scoped.getContext(); - ParameterData parameter = parameters[i]; - - String[] defaultValue = parameter.getDefaultValue(); - if (defaultValue != null) { - try { - return parameter.getBinding().bind(parameter, new StringArgumentStack(context, defaultValue, false), false); - } catch (MissingParameterException e) { - throw new ParametricException( - "The default value of the parameter using the binding " + - parameter.getBinding().getClass() + " in the method\n" + - method.toGenericString() + "\nis invalid"); - } - } - - return null; - } - - - /** - * Check to see if all arguments, including flag arguments, were consumed. - * - * @param scoped the argument scope - * @throws UnconsumedParameterException thrown if parameters were not consumed - */ - public void checkUnconsumed(ContextArgumentStack scoped) throws UnconsumedParameterException { - CommandContext context = scoped.getContext(); - String unconsumed; - String unconsumedFlags = getUnusedFlags(context); - - if ((unconsumed = scoped.getUnconsumed()) != null) { - throw new UnconsumedParameterException(unconsumed + " " + unconsumedFlags); - } - - if (unconsumedFlags != null) { - throw new UnconsumedParameterException(unconsumedFlags); - } - } - - /** - * Get any unused flag arguments. - * - * @param context the command context - */ - public String getUnusedFlags(CommandContext context) { - if (!anyFlags) { - Set unusedFlags = null; - for (char flag : context.getFlags()) { - boolean found = false; - - if (legacyFlags.contains(flag)) { - break; - } - - for (ParameterData parameter : parameters) { - Character paramFlag = parameter.getFlag(); - if (paramFlag != null && flag == paramFlag) { - found = true; - break; - } - } - - if (!found) { - if (unusedFlags == null) { - unusedFlags = new HashSet(); - } - unusedFlags.add(flag); - } - } - - if (unusedFlags != null) { - StringBuilder builder = new StringBuilder(); - for (Character flag : unusedFlags) { - builder.append("-").append(flag).append(" "); - } - - return builder.toString().trim(); - } - } - - return null; + @Override + public String toString() { + return method.toGenericString(); } /** @@ -573,7 +360,8 @@ public class ParametricCallable implements CommandCallable { } } - public static Class inject() { + + public static Class inject() { return ParametricCallable.class; } } \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/worldedit/util/command/parametric/StringArgumentStack.java b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/StringArgumentStack.java index 52bb1d50..cdb5b1d2 100644 --- a/core/src/main/java/com/sk89q/worldedit/util/command/parametric/StringArgumentStack.java +++ b/core/src/main/java/com/sk89q/worldedit/util/command/parametric/StringArgumentStack.java @@ -146,7 +146,7 @@ public class StringArgumentStack implements ArgumentStack { */ @Override public String reset() { - String value = context.getString(markedIndex, index - 1); + String value = (index - 1 > markedIndex) ? context.getString(markedIndex, index - 1) : ""; index = markedIndex; return value; } diff --git a/core/src/main/resources/cs_adv.js b/core/src/main/resources/cs_adv.js new file mode 100644 index 00000000..f619c3e4 --- /dev/null +++ b/core/src/main/resources/cs_adv.js @@ -0,0 +1,51 @@ +{ + var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; + var ARGUMENT_NAMES = /([^\s,]+)/g; + function getParamNames(func) { + var a = func.toString().replace(STRIP_COMMENTS, ''); + var r = a.slice(a.indexOf('(')+1, a.indexOf(')')).match(ARGUMENT_NAMES); + var l = new java.util.ArrayList(); + if(r !== null) { + for (var i = 0; i < r.length; i++) { + l.add(r[i]); + } + } + return l; + } + + function getAllFunctions(){ + var a = new java.util.ArrayList(); + for (var f in this){ + if (this.hasOwnProperty(f) && this[f] instanceof Function && !/a/i.test(f)){ + a.add(this[f]); + } + } + return a; + } + + var functions = getAllFunctions(); + var commands = new java.util.ArrayList() + for (var i = 0; i < functions.length; i++) { + var f = functions[i]; + if (f.hasOwnProperty('desc')) + { + if (!f.hasOwnProperty('permission')) f.permission = "fawe.use"; + if (!f.hasOwnProperty('aliases')) f.aliases = [f.name]; + if (!f.hasOwnProperty('queued')) f.queued = true; + var cmd = com.boydti.fawe.config.Commands.fromArgs(f.aliases, f.usage, f.desc, f.min, f.max, f.flags, f.help, f.queued); + var man = com.sk89q.worldedit.extension.platform.CommandManager.getInstance(); + var builder = man.getBuilder(); + var args = getParamNames(f); + + var wrap = Java.extend(java.util.function.Function, { + apply: function(a) { + return f.apply(null, a); + } + }); + var w2 = new wrap(); + var callable = new com.sk89q.worldedit.util.command.parametric.FunctionParametricCallable(builder, "", cmd, f.permission, args, w2); + commands.add(callable); + } + } + commands; +} \ No newline at end of file