diff --git a/build.gradle b/build.gradle index a5f7a101c..f4f48eac9 100644 --- a/build.gradle +++ b/build.gradle @@ -89,7 +89,7 @@ subprojects { ivy { url 'https://ci.athion.net/job' layout 'pattern', { - artifact '/[organisation]/[revision]/artifact/[module].[ext]' + artifact '/[organisation]/[module]/artifact/[revision].[ext]' } } } diff --git a/worldedit-bukkit/build.gradle b/worldedit-bukkit/build.gradle index fa20b2491..a8b71befb 100644 --- a/worldedit-bukkit/build.gradle +++ b/worldedit-bukkit/build.gradle @@ -13,6 +13,9 @@ configurations.all { Configuration it -> it.resolutionStrategy { ResolutionStrategy rs -> rs.force("com.google.guava:guava:21.0") } + it.resolutionStrategy { ResolutionStrategy rs -> + rs.force("it.unimi.dsi:fastutil:8.2.1") + } } dependencies { @@ -21,6 +24,7 @@ dependencies { api project(':worldedit-libs:core') // TODO remove once core can compile api project(':worldedit-libs:bukkit') compileOnly 'com.sk89q:dummypermscompat:1.10' + compile "it.unimi.dsi:fastutil:8.2.1" testCompile 'org.mockito:mockito-core:1.9.0-rc1' implementation('org.apache.logging.log4j:log4j-slf4j-impl:2.8.1'){transitive = false} compile 'com.destroystokyo.paper:paper-api:1.14.4-R0.1-SNAPSHOT' @@ -42,6 +46,12 @@ dependencies { implementation('com.wasteofplastic:askyblock:3.0.8.2'){transitive = false} } +shadowJar { + relocate("it.unimi.dsi.fastutil", "com.sk89q.worldedit.bukkit.fastutil") { + include("it.unimi.dsi:fastutil") + } +} + processResources { from('src/main/resources') { expand( diff --git a/worldedit-core/build.gradle b/worldedit-core/build.gradle index 815f39a09..53e79bbda 100644 --- a/worldedit-core/build.gradle +++ b/worldedit-core/build.gradle @@ -15,6 +15,9 @@ configurations.all { Configuration it -> it.resolutionStrategy { ResolutionStrategy rs -> rs.force("com.google.guava:guava:21.0") } + it.resolutionStrategy { ResolutionStrategy rs -> + rs.force("it.unimi.dsi:fastutil:8.2.1") + } } dependencies { @@ -28,6 +31,7 @@ dependencies { compile 'com.google.code.gson:gson:2.8.0' compile 'com.googlecode.json-simple:json-simple:1.1.1' compile 'org.slf4j:slf4j-api:1.7.26' + compile "it.unimi.dsi:fastutil:8.2.1" compileOnly project(':worldedit-libs:core:ap') annotationProcessor project(':worldedit-libs:core:ap') @@ -46,6 +50,11 @@ dependencies { compile 'com.mojang:datafixerupper:1.0.20' compile 'com.github.luben:zstd-jni:1.1.1' compile 'co.aikar:fastutil-lite:1.0' + testImplementation ("org.junit.jupiter:junit-jupiter-api:5.5.0") + testImplementation ("org.junit.jupiter:junit-jupiter-params:5.5.0") + testImplementation ("org.mockito:mockito-core:3.0.0") + testImplementation ("org.mockito:mockito-junit-jupiter:3.0.0") + testRuntime ("org.junit.jupiter:junit-jupiter-engine:5.5.0") } tasks.withType(JavaCompile).configureEach { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/util/RandomTextureUtil.java b/worldedit-core/src/main/java/com/boydti/fawe/util/RandomTextureUtil.java index 715eb141e..fbcc60bab 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/util/RandomTextureUtil.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/util/RandomTextureUtil.java @@ -1,6 +1,7 @@ package com.boydti.fawe.util; import com.sk89q.worldedit.world.block.BlockType; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import java.io.FileNotFoundException; @@ -80,7 +81,7 @@ public class RandomTextureUtil extends CachedTextureUtil { @Override public BlockType getNearestBlock(int color) { - int offsetColor = offsets.getOrDefault(color, 0); + int offsetColor = offsets.getOrDefault((Object) color, 0); if (offsetColor != 0) { offsetColor = addRandomColor(color, offsetColor); } else { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/util/ReflectionUtils.java b/worldedit-core/src/main/java/com/boydti/fawe/util/ReflectionUtils.java index 8c47fcf17..c7a4d706a 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/util/ReflectionUtils.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/util/ReflectionUtils.java @@ -168,73 +168,73 @@ public class ReflectionUtils { } } - public static Object getHandle(final Object wrapper) { + public static Object getHandle(Object wrapper) { final Method getHandle = makeMethod(wrapper.getClass(), "getHandle"); return callMethod(getHandle, wrapper); } //Utils - public static Method makeMethod(final Class clazz, final String methodName, final Class... parameters) { + public static Method makeMethod(Class clazz, String methodName, Class... parameters) { try { return clazz.getDeclaredMethod(methodName, parameters); - } catch (final NoSuchMethodException ex) { + } catch (NoSuchMethodException ex) { return null; - } catch (final Exception ex) { + } catch (Exception ex) { throw new RuntimeException(ex); } } @SuppressWarnings("unchecked") - public static T callMethod(final Method method, final Object instance, final Object... parameters) { + public static T callMethod(Method method, Object instance, Object... parameters) { if (method == null) { throw new RuntimeException("No such method"); } method.setAccessible(true); try { return (T) method.invoke(instance, parameters); - } catch (final InvocationTargetException ex) { + } catch (InvocationTargetException ex) { throw new RuntimeException(ex.getCause()); - } catch (final Exception ex) { + } catch (Exception ex) { throw new RuntimeException(ex); } } @SuppressWarnings("unchecked") - public static Constructor makeConstructor(final Class clazz, final Class... parameterTypes) { + public static Constructor makeConstructor(Class clazz, Class... parameterTypes) { try { return (Constructor) clazz.getConstructor(parameterTypes); - } catch (final NoSuchMethodException ex) { + } catch (NoSuchMethodException ex) { return null; - } catch (final Exception ex) { + } catch (Exception ex) { throw new RuntimeException(ex); } } - public static T callConstructor(final Constructor constructor, final Object... paramaters) { + public static T callConstructor(Constructor constructor, Object... paramaters) { if (constructor == null) { throw new RuntimeException("No such constructor"); } constructor.setAccessible(true); try { return constructor.newInstance(paramaters); - } catch (final InvocationTargetException ex) { + } catch (InvocationTargetException ex) { throw new RuntimeException(ex.getCause()); - } catch (final Exception ex) { + } catch (Exception ex) { throw new RuntimeException(ex); } } - public static Field makeField(final Class clazz, final String name) { + public static Field makeField(Class clazz, String name) { try { return clazz.getDeclaredField(name); - } catch (final NoSuchFieldException ex) { + } catch (NoSuchFieldException ex) { return null; - } catch (final Exception ex) { + } catch (Exception ex) { throw new RuntimeException(ex); } } - public static Field findField(final Class clazz, final Class type, int hasMods, int noMods) { + public static Field findField(Class clazz, Class type, int hasMods, int noMods) { for (Field field : clazz.getDeclaredFields()) { if (type == null || type.isAssignableFrom(field.getType())) { int mods = field.getModifiers(); @@ -246,7 +246,7 @@ public class ReflectionUtils { return null; } - public static Field findField(final Class clazz, final Class type) { + public static Field findField(Class clazz, Class type) { for (Field field : clazz.getDeclaredFields()) { if (field.getType() == type) { return setAccessible(field); @@ -255,11 +255,11 @@ public class ReflectionUtils { return null; } - public static Method findMethod(final Class clazz, final Class returnType, Class... params) { + public static Method findMethod(Class clazz, Class returnType, Class... params) { return findMethod(clazz, 0, returnType, params); } - public static Method findMethod(final Class clazz, int index, int hasMods, int noMods, final Class returnType, Class... params) { + public static Method findMethod(Class clazz, int index, int hasMods, int noMods, Class returnType, Class... params) { outer: for (Method method : sortMethods(clazz.getDeclaredMethods())) { if (returnType == null || method.getReturnType() == returnType) { @@ -296,49 +296,49 @@ public class ReflectionUtils { return fields; } - public static Method findMethod(final Class clazz, int index, final Class returnType, Class... params) { + public static Method findMethod(Class clazz, int index, Class returnType, Class... params) { return findMethod(clazz, index, 0, 0, returnType, params); } - public static T setAccessible(final T ao) { + public static T setAccessible(T ao) { ao.setAccessible(true); return ao; } @SuppressWarnings("unchecked") - public static T getField(final Field field, final Object instance) { + public static T getField(Field field, Object instance) { if (field == null) { throw new RuntimeException("No such field"); } field.setAccessible(true); try { return (T) field.get(instance); - } catch (final Exception ex) { + } catch (Exception ex) { throw new RuntimeException(ex); } } - public static void setField(final Field field, final Object instance, final Object value) { + public static void setField(Field field, Object instance, Object value) { if (field == null) { throw new RuntimeException("No such field"); } field.setAccessible(true); try { field.set(instance, value); - } catch (final Exception ex) { + } catch (Exception ex) { throw new RuntimeException(ex); } } - public static Class getClass(final String name) { + public static Class getClass(String name) { try { return Class.forName(name); - } catch (final ClassNotFoundException ex) { + } catch (ClassNotFoundException ex) { return null; } } - public static Class getClass(final String name, final Class superClass) { + public static Class getClass(String name, Class superClass) { try { return Class.forName(name).asSubclass(superClass); } catch (ClassCastException | ClassNotFoundException ex) { @@ -353,7 +353,7 @@ public class ReflectionUtils { * @param clazz class * @return RefClass based on passed class */ - public static RefClass getRefClass(final Class clazz) { + public static RefClass getRefClass(Class clazz) { return new RefClass(clazz); } @@ -363,7 +363,7 @@ public class ReflectionUtils { public static class RefClass { private final Class clazz; - private RefClass(final Class clazz) { + private RefClass(Class clazz) { this.clazz = clazz; } @@ -382,7 +382,7 @@ public class ReflectionUtils { * @param object the object to check * @return true if object is an instance of this class */ - public boolean isInstance(final Object object) { + public boolean isInstance(Object object) { return this.clazz.isInstance(object); } @@ -394,11 +394,11 @@ public class ReflectionUtils { * @return RefMethod object * @throws RuntimeException if method not found */ - public RefMethod getMethod(final String name, final Object... types) throws NoSuchMethodException { + public RefMethod getMethod(String name, Object... types) throws NoSuchMethodException { try { final Class[] classes = new Class[types.length]; int i = 0; - for (final Object e : types) { + for (Object e : types) { if (e instanceof Class) { classes[i++] = (Class) e; } else if (e instanceof RefClass) { @@ -409,10 +409,10 @@ public class ReflectionUtils { } try { return new RefMethod(this.clazz.getMethod(name, classes)); - } catch (final NoSuchMethodException ignored) { + } catch (NoSuchMethodException ignored) { return new RefMethod(this.clazz.getDeclaredMethod(name, classes)); } - } catch (final Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } @@ -424,11 +424,11 @@ public class ReflectionUtils { * @return RefMethod object * @throws RuntimeException if constructor not found */ - public RefConstructor getConstructor(final Object... types) { + public RefConstructor getConstructor(Object... types) { try { final Class[] classes = new Class[types.length]; int i = 0; - for (final Object e : types) { + for (Object e : types) { if (e instanceof Class) { classes[i++] = (Class) e; } else if (e instanceof RefClass) { @@ -439,10 +439,10 @@ public class ReflectionUtils { } try { return new RefConstructor(this.clazz.getConstructor(classes)); - } catch (final NoSuchMethodException ignored) { + } catch (NoSuchMethodException ignored) { return new RefConstructor(this.clazz.getDeclaredConstructor(classes)); } - } catch (final Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } @@ -454,10 +454,10 @@ public class ReflectionUtils { * @return RefMethod object * @throws RuntimeException if method not found */ - public RefMethod findMethod(final Object... types) { + public RefMethod findMethod(Object... types) { final Class[] classes = new Class[types.length]; int t = 0; - for (final Object e : types) { + for (Object e : types) { if (e instanceof Class) { classes[t++] = (Class) e; } else if (e instanceof RefClass) { @@ -470,12 +470,12 @@ public class ReflectionUtils { Collections.addAll(methods, this.clazz.getMethods()); Collections.addAll(methods, this.clazz.getDeclaredMethods()); findMethod: - for (final Method m : methods) { + for (Method m : methods) { final Class[] methodTypes = m.getParameterTypes(); if (methodTypes.length != classes.length) { continue; } - for (final Class aClass : classes) { + for (Class aClass : classes) { if (!Arrays.equals(classes, methodTypes)) { continue findMethod; } @@ -492,12 +492,12 @@ public class ReflectionUtils { * @return RefMethod object * @throws RuntimeException if method not found */ - public RefMethod findMethodByName(final String... names) { + public RefMethod findMethodByName(String... names) { final List methods = new ArrayList<>(); Collections.addAll(methods, this.clazz.getMethods()); Collections.addAll(methods, this.clazz.getDeclaredMethods()); - for (final Method m : methods) { - for (final String name : names) { + for (Method m : methods) { + for (String name : names) { if (m.getName().equals(name)) { return new RefMethod(m); } @@ -513,7 +513,7 @@ public class ReflectionUtils { * @return RefMethod * @throws RuntimeException if method not found */ - public RefMethod findMethodByReturnType(final RefClass type) { + public RefMethod findMethodByReturnType(RefClass type) { return this.findMethodByReturnType(type.clazz); } @@ -531,7 +531,7 @@ public class ReflectionUtils { final List methods = new ArrayList<>(); Collections.addAll(methods, this.clazz.getMethods()); Collections.addAll(methods, this.clazz.getDeclaredMethods()); - for (final Method m : methods) { + for (Method m : methods) { if (type.equals(m.getReturnType())) { return new RefMethod(m); } @@ -546,11 +546,11 @@ public class ReflectionUtils { * @return RefConstructor * @throws RuntimeException if constructor not found */ - public RefConstructor findConstructor(final int number) { + public RefConstructor findConstructor(int number) { final List constructors = new ArrayList<>(); Collections.addAll(constructors, this.clazz.getConstructors()); Collections.addAll(constructors, this.clazz.getDeclaredConstructors()); - for (final Constructor m : constructors) { + for (Constructor m : constructors) { if (m.getParameterTypes().length == number) { return new RefConstructor(m); } @@ -565,14 +565,14 @@ public class ReflectionUtils { * @return RefField * @throws RuntimeException if field not found */ - public RefField getField(final String name) { + public RefField getField(String name) { try { try { return new RefField(this.clazz.getField(name)); - } catch (final NoSuchFieldException ignored) { + } catch (NoSuchFieldException ignored) { return new RefField(this.clazz.getDeclaredField(name)); } - } catch (final Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } @@ -584,7 +584,7 @@ public class ReflectionUtils { * @return RefField * @throws RuntimeException if field not found */ - public RefField findField(final RefClass type) { + public RefField findField(RefClass type) { return this.findField(type.clazz); } @@ -602,7 +602,7 @@ public class ReflectionUtils { final List fields = new ArrayList<>(); Collections.addAll(fields, this.clazz.getFields()); Collections.addAll(fields, this.clazz.getDeclaredFields()); - for (final Field f : fields) { + for (Field f : fields) { if (type.equals(f.getType())) { return new RefField(f); } @@ -617,7 +617,7 @@ public class ReflectionUtils { public static class RefMethod { private final Method method; - private RefMethod(final Method method) { + private RefMethod(Method method) { this.method = method; method.setAccessible(true); } @@ -649,7 +649,7 @@ public class ReflectionUtils { * @param e object to which the method is applied * @return RefExecutor with method call(...) */ - public RefExecutor of(final Object e) { + public RefExecutor of(Object e) { return new RefExecutor(e); } @@ -659,10 +659,10 @@ public class ReflectionUtils { * @param params sent parameters * @return return value */ - public Object call(final Object... params) { + public Object call(Object... params) { try { return this.method.invoke(null, params); - } catch (final Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } @@ -670,7 +670,7 @@ public class ReflectionUtils { public class RefExecutor { final Object e; - public RefExecutor(final Object e) { + public RefExecutor(Object e) { this.e = e; } @@ -681,10 +681,10 @@ public class ReflectionUtils { * @return return value * @throws RuntimeException if something went wrong */ - public Object call(final Object... params) { + public Object call(Object... params) { try { return RefMethod.this.method.invoke(this.e, params); - } catch (final Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } @@ -697,7 +697,7 @@ public class ReflectionUtils { public static class RefConstructor { private final Constructor constructor; - private RefConstructor(final Constructor constructor) { + private RefConstructor(Constructor constructor) { this.constructor = constructor; constructor.setAccessible(true); } @@ -723,10 +723,10 @@ public class ReflectionUtils { * @return new object * @throws RuntimeException if something went wrong */ - public Object create(final Object... params) { + public Object create(Object... params) { try { return this.constructor.newInstance(params); - } catch (final Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } @@ -735,7 +735,7 @@ public class ReflectionUtils { public static class RefField { private final Field field; - private RefField(final Field field) { + private RefField(Field field) { this.field = field; field.setAccessible(true); } @@ -767,14 +767,14 @@ public class ReflectionUtils { * @param e applied object * @return RefExecutor with getter and setter */ - public RefExecutor of(final Object e) { + public RefExecutor of(Object e) { return new RefExecutor(e); } public class RefExecutor { final Object e; - public RefExecutor(final Object e) { + public RefExecutor(Object e) { this.e = e; } @@ -783,10 +783,10 @@ public class ReflectionUtils { * * @param param value */ - public void set(final Object param) { + public void set(Object param) { try { RefField.this.field.set(this.e, param); - } catch (final Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } @@ -799,7 +799,7 @@ public class ReflectionUtils { public Object get() { try { return RefField.this.field.get(this.e); - } catch (final Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalConfiguration.java b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalConfiguration.java index 05e6ead54..e9db84e06 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalConfiguration.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalConfiguration.java @@ -80,6 +80,7 @@ public abstract class LocalConfiguration { public int butcherMaxRadius = -1; public boolean allowSymlinks = false; public boolean serverSideCUI = true; + public boolean extendedYLimit = false; protected String[] getDefaultDisallowedBlocks() { List blockTypes = Lists.newArrayList( diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index 72ec1431b..92a8a0e60 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -95,6 +95,7 @@ import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.SingleBlockTypeMask; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.internal.annotation.ClipboardMask; import com.sk89q.worldedit.internal.annotation.Range; import com.sk89q.worldedit.internal.expression.Expression; import com.sk89q.worldedit.internal.expression.runtime.EvaluationException; @@ -153,9 +154,9 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.blendball") public BrushSettings blendBallBrush(Player player, LocalSession session, @Arg(desc = "The radius to sample for blending", def = "5") - Expression radiusOpt, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); - return set(session, context, new BlendBall()).setSize(radiusOpt); + Expression radius, InjectedValueAccess context) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); + return set(session, context, new BlendBall()).setSize(radius); } @Command( @@ -165,9 +166,9 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.erode") public BrushSettings erodeBrush(Player player, LocalSession session, @Arg(desc = "The radius for eroding", def = "5") - Expression radiusOpt, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); - return set(session, context, new ErodeBrush()).setSize(radiusOpt); + Expression radius, InjectedValueAccess context) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); + return set(session, context, new ErodeBrush()).setSize(radius); } @Command( @@ -177,9 +178,9 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.pull") public BrushSettings pullBrush(Player player, LocalSession session, @Arg(desc = "The radius to sample for blending", def = "5") - Expression radiusOpt, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); - return set(session, context, new RaiseBrush()).setSize(radiusOpt); + Expression radius, InjectedValueAccess context) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); + return set(session, context, new RaiseBrush()).setSize(radius); } @Command( @@ -189,9 +190,9 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.sphere") public BrushSettings circleBrush(Player player, EditSession editSession, LocalSession session, Pattern fill, @Arg(desc = "The radius to sample for blending", def = "5") - Expression radiusOpt, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); - return set(session, context, new CircleBrush(player)).setSize(radiusOpt).setFill(fill); + Expression radius, InjectedValueAccess context) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); + return set(session, context, new CircleBrush(player)).setSize(radius).setFill(fill); } @Command( @@ -204,14 +205,14 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.recursive") public BrushSettings recursiveBrush(Player player, LocalSession session, EditSession editSession, Pattern fill, @Arg(desc = "The radius to sample for blending", def = "5") - Expression radiusOpt, + Expression radius, @Switch(name = 'd', desc = "Apply in depth first order") boolean depthFirst, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); return set(session, context, new RecurseBrush(depthFirst)) - .setSize(radiusOpt) + .setSize(radius) .setFill(fill) .setMask(new IdMask(editSession)); } @@ -224,17 +225,17 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.line") public BrushSettings lineBrush(Player player, LocalSession session, Pattern fill, @Arg(desc = "The radius to sample for blending", def = "0") - Expression radiusOpt, + Expression radius, @Switch(name = 'h', desc = "Create only a shell") boolean shell, @Switch(name = 's', desc = "Selects the clicked point after drawing") boolean select, @Switch(name = 'f', desc = "Create a flat line") boolean flat, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); return set(session, context, new LineBrush(shell, select, flat)) - .setSize(radiusOpt) + .setSize(radius) .setFill(fill); } @@ -251,12 +252,12 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.spline") public BrushSettings splineBrush(Player player, EditSession editSession, LocalSession session, Pattern fill, @Arg(desc = "The radius to sample for blending", def = "25") - Expression radiusOpt, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); - player.print(BBC.BRUSH_SPLINE.format(radiusOpt)); + Expression radius, InjectedValueAccess context) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); + player.print(BBC.BRUSH_SPLINE.format(radius)); return set(session, context, new SplineBrush(player, session)) - .setSize(radiusOpt) + .setSize(radius) .setFill(fill); } @@ -282,7 +283,7 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.spline") public BrushSettings catenaryBrush(LocalSession session, Pattern fill, @Arg(def = "1.2", desc = "Length of wire compared to distance between points") @Range(min = 1) double lengthFactor, @Arg(desc = "The radius to sample for blending", def = "0") - Expression radiusOpt, + Expression radius, @Switch(name = 'h', desc = "Create only a shell") boolean shell, @Switch(name = 's', desc = "Select the clicked point after drawing") @@ -290,11 +291,11 @@ public class BrushCommands { @Switch(name = 'd', desc = "sags the catenary toward the facing direction") boolean facingDirection, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); Brush brush = new CatenaryBrush(shell, select, facingDirection, lengthFactor); return set(session, context, new CatenaryBrush(shell, select, facingDirection, lengthFactor)) - .setSize(radiusOpt) + .setSize(radius) .setFill(fill); } @@ -308,12 +309,12 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.surfacespline") // 0, 0, 0, 10, 0, public BrushSettings surfaceSpline(Player player, LocalSession session, Pattern fill, @Arg(desc = "The radius to sample for blending", def = "0") - Expression radiusOpt, @Arg(name = "tension", desc = "double", def = "0") double tension, @Arg(name = "bias", desc = "double", def = "0") double bias, @Arg(name = "continuity", desc = "double", def = "0") double continuity, @Arg(name = "quality", desc = "double", def = "10") double quality, InjectedValueAccess context) throws WorldEditException { - player.print(BBC.BRUSH_SPLINE.format(radiusOpt)); - worldEdit.checkMaxBrushRadius(radiusOpt); + Expression radius, @Arg(name = "tension", desc = "double", def = "0") double tension, @Arg(name = "bias", desc = "double", def = "0") double bias, @Arg(name = "continuity", desc = "double", def = "0") double continuity, @Arg(name = "quality", desc = "double", def = "10") double quality, InjectedValueAccess context) throws WorldEditException { + player.print(BBC.BRUSH_SPLINE.format(radius)); + worldEdit.checkMaxBrushRadius(radius); return set(session, context, new SurfaceSpline(tension, bias, continuity, quality)) - .setSize(radiusOpt) + .setSize(radius) .setFill(fill); } @@ -343,12 +344,12 @@ public class BrushCommands { @Arg(desc = "The pattern of blocks to set") Pattern pattern, @Arg(desc = "The radius of the sphere", def = "2") - Expression radiusOpt, + Expression radius, @Switch(name = 'h', desc = "Create hollow spheres instead") boolean hollow, @Switch(name = 'f', desc = "Create falling spheres instead") boolean falling, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); Brush brush; if (hollow) { brush = new HollowSphereBrush(); @@ -371,7 +372,7 @@ public class BrushCommands { } return set(session, context, brush) - .setSize(radiusOpt) + .setSize(radius) .setFill(pattern); } @@ -385,12 +386,12 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.shatter") public BrushSettings shatterBrush(Player player, EditSession editSession, LocalSession session, Pattern fill, @Arg(desc = "The radius to sample for blending", def = "10") - Expression radiusOpt, + Expression radius, @Arg(desc = "Lines", def = "10") int count, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); return set(session, context, new ShatterBrush(count)) - .setSize(radiusOpt) + .setSize(radius) .setFill(fill) .setMask(new ExistingBlockMask(editSession)); } @@ -402,14 +403,14 @@ public class BrushCommands { ) @CommandPermissions("worldedit.brush.stencil") public BrushSettings stencilBrush(Player player, LocalSession session, Pattern fill, - @Arg(name = "radius", desc = "Expression", def = "5") Expression radiusOpt, + @Arg(name = "radius", desc = "Expression", def = "5") Expression radius, @Arg(name = "image", desc = "String", def = "") String image, @Arg(def = "0", desc = "rotation") @Range(min = 0, max = 360) int rotation, @Arg(name = "yscale", desc = "double", def = "1") double yscale, @Switch(name = 'w', desc = "Apply at maximum saturation") boolean onlyWhite, @Switch(name = 'r', desc = "Apply random rotation") boolean randomRotate, InjectedValueAccess context) throws WorldEditException, FileNotFoundException { - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); InputStream stream = getHeightmapStream(image); HeightBrush brush; try { @@ -422,7 +423,7 @@ public class BrushCommands { } return set(session, context, brush) - .setSize(radiusOpt) + .setSize(radius) .setFill(fill); } @@ -432,14 +433,14 @@ public class BrushCommands { desc = "Use a height map to paint a surface", descFooter = "Use a height map to paint any surface.\n") @CommandPermissions("worldedit.brush.stencil") - public BrushSettings imageBrush(LocalSession session, @Arg(name = "radius", desc = "Expression", def = "5") Expression radiusOpt, + public BrushSettings imageBrush(LocalSession session, @Arg(name = "radius", desc = "Expression", def = "5") Expression radius, ProvideBindings.ImageUri imageUri, @Arg(def = "1", desc = "scale height") @Range(min = Double.MIN_NORMAL) double yscale, @Switch(name = 'a', desc = "Use image Alpha") boolean alpha, @Switch(name = 'f', desc = "Blend the image with existing terrain") boolean fadeOut, InjectedValueAccess context) throws WorldEditException, IOException { BufferedImage image = imageUri.load(); - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); if (yscale != 1) { ImageUtil.scaleAlpha(image, yscale); alpha = true; @@ -451,7 +452,7 @@ public class BrushCommands { ImageBrush brush = new ImageBrush(image, session, alpha); return set(session, context, brush) - .setSize(radiusOpt); + .setSize(radius); } @Command( @@ -465,10 +466,10 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.surface") public BrushSettings surfaceBrush(LocalSession session, Pattern fill, @Arg(name = "radius", desc = "Expression", def = "5") - Expression radiusOpt, + Expression radius, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); - return set(session, context, new SurfaceSphereBrush()).setFill(fill).setSize(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); + return set(session, context, new SurfaceSphereBrush()).setFill(fill).setSize(radius); } @Command( @@ -478,8 +479,8 @@ public class BrushCommands { "Video: https://youtu.be/RPZIaTbqoZw?t=34s" ) @CommandPermissions("worldedit.brush.scatter") - public BrushSettings scatterBrush(LocalSession session, Pattern fill, @Arg(name = "radius", desc = "Expression", def = "5") Expression radiusOpt, @Arg(name = "points", desc = "double", def = "5") double pointsOpt, @Arg(name = "distance", desc = "double", def = "1") double distanceOpt, @Switch(name = 'o', desc = "Overlay the block") boolean overlay, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + public BrushSettings scatterBrush(LocalSession session, Pattern fill, @Arg(name = "radius", desc = "Expression", def = "5") Expression radius, @Arg(name = "points", desc = "double", def = "5") double pointsOpt, @Arg(name = "distance", desc = "double", def = "1") double distanceOpt, @Switch(name = 'o', desc = "Overlay the block") boolean overlay, InjectedValueAccess context) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); Brush brush; if (overlay) { brush = new ScatterOverlayBrush((int) pointsOpt, (int) distanceOpt); @@ -488,7 +489,7 @@ public class BrushCommands { } return set(session, context, brush) - .setSize(radiusOpt) + .setSize(radius) .setFill(fill); } @@ -498,8 +499,8 @@ public class BrushCommands { desc = "Scatter a schematic on a surface" ) @CommandPermissions("worldedit.brush.populateschematic") - public BrushSettings scatterSchemBrush(Player player, LocalSession session, Mask mask, @Arg(name = "clipboard", desc = "Clipboard uri") String clipboardStr, @Arg(name = "radius", desc = "Expression", def = "30") Expression radiusOpt, @Arg(name = "density", desc = "double", def = "50") double density, @Switch(name = 'r', desc = "Apply random rotation") boolean rotate, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + public BrushSettings scatterSchemBrush(Player player, LocalSession session, Mask mask, @Arg(name = "clipboard", desc = "Clipboard uri") String clipboardStr, @Arg(name = "radius", desc = "Expression", def = "30") Expression radius, @Arg(name = "density", desc = "double", def = "50") double density, @Switch(name = 'r', desc = "Apply random rotation") boolean rotate, InjectedValueAccess context) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); try { MultiClipboardHolder clipboards = ClipboardFormats.loadAllFromInput(player, clipboardStr, null, true); if (clipboards == null) { @@ -513,7 +514,7 @@ public class BrushCommands { } return set(session, context, - new PopulateSchem(mask, holders, (int) density, rotate)).setSize(radiusOpt); + new PopulateSchem(mask, holders, (int) density, rotate)).setSize(radius); } catch (IOException e) { throw new RuntimeException(e); } @@ -541,9 +542,9 @@ public class BrushCommands { "Note: The seeds define how many splotches there are, recursion defines how large, solid defines whether the pattern is applied per seed, else per block." ) @CommandPermissions("worldedit.brush.splatter") - public BrushSettings splatterBrush(LocalSession session, Pattern fill, @Arg(name = "radius", desc = "Expression", def = "5") Expression radiusOpt, @Arg(name = "points", desc = "double", def = "1") double pointsOpt, @Arg(name = "recursion", desc = "double", def = "5") double recursion, @Arg(name = "solid", desc = "boolean", def = "true") boolean solid, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); - return set(session, context, new SplatterBrush((int) pointsOpt, (int) recursion, solid)).setSize(radiusOpt).setFill(fill); + public BrushSettings splatterBrush(LocalSession session, Pattern fill, @Arg(name = "radius", desc = "Expression", def = "5") Expression radius, @Arg(name = "points", desc = "double", def = "1") double pointsOpt, @Arg(name = "recursion", desc = "double", def = "5") double recursion, @Arg(name = "solid", desc = "boolean", def = "true") boolean solid, InjectedValueAccess context) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); + return set(session, context, new SplatterBrush((int) pointsOpt, (int) recursion, solid)).setSize(radius).setFill(fill); } @Command( @@ -574,13 +575,13 @@ public class BrushCommands { @Arg(desc = "The pattern of blocks to set") Pattern pattern, @Arg(desc = "The radius of the cylinder", def = "2") - Expression radiusOpt, + Expression radius, @Arg(desc = "The height of the cylinder", def = "1") int height, @Switch(name = 'h', desc = "Create hollow cylinders instead") boolean hollow, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); worldEdit.checkMaxBrushRadius(height); BrushSettings settings; @@ -589,7 +590,7 @@ public class BrushCommands { } else { settings = set(session, context, new CylinderBrush(height)); } - settings.setSize(radiusOpt) + settings.setSize(radius) .setFill(pattern); return settings; } @@ -604,7 +605,7 @@ public class BrushCommands { "stood relative to the copied area when you copied it." ) @CommandPermissions("worldedit.brush.clipboard") - public BrushSettings clipboardBrush(LocalSession session, + public BrushSettings clipboardBrush(Player player,LocalSession session, @Switch(name = 'a', desc = "Don't paste air from the clipboard") boolean ignoreAir, @Switch(name = 'o', desc = "Paste starting at the target location, instead of centering on it") @@ -614,17 +615,21 @@ public class BrushCommands { @Switch(name = 'b', desc = "Paste biomes if available") boolean pasteBiomes, @ArgFlag(name = 'm', desc = "Skip blocks matching this mask in the clipboard", def = "") + @ClipboardMask Mask sourceMask, InjectedValueAccess context) throws WorldEditException { ClipboardHolder holder = session.getClipboard(); Clipboard clipboard = holder.getClipboard(); + ClipboardHolder newHolder = new ClipboardHolder(clipboard); + newHolder.setTransform(holder.getTransform()); BlockVector3 size = clipboard.getDimensions(); - worldEdit.checkMaxBrushRadius(size.getBlockX()); - worldEdit.checkMaxBrushRadius(size.getBlockY()); - worldEdit.checkMaxBrushRadius(size.getBlockZ()); - return set(session, context, new ClipboardBrush(holder, ignoreAir, usingOrigin, !skipEntities, pasteBiomes, sourceMask)); + worldEdit.checkMaxBrushRadius(size.getBlockX() / 2D - 1); + worldEdit.checkMaxBrushRadius(size.getBlockY() / 2D - 1); + worldEdit.checkMaxBrushRadius(size.getBlockZ() / 2D - 1); + + return set(session, context, new ClipboardBrush(newHolder, ignoreAir, usingOrigin, !skipEntities, pasteBiomes, sourceMask)); } @Command( @@ -635,12 +640,12 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.smooth") public BrushSettings smoothBrush(Player player, LocalSession session, EditSession editSession, @Arg(desc = "The radius to sample for softening", def = "2") - Expression radiusOpt, + Expression radius, @Arg(desc = "The number of iterations to perform", def = "4") int iterations, @Arg(desc = "The mask of blocks to use for the heightmap", def = "") Mask maskOpt, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); FawePlayer fp = FawePlayer.wrap(player); FaweLimit limit = Settings.IMP.getLimit(fp); @@ -648,7 +653,7 @@ public class BrushCommands { return set(session, context, new SmoothBrush(iterations, maskOpt)) - .setSize(radiusOpt); + .setSize(radius); } @Command( @@ -659,14 +664,14 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.ex") public BrushSettings extinguishBrush(Player player, LocalSession session, EditSession editSession, @Arg(desc = "The radius to extinguish", def = "5") - Expression radiusOpt, + Expression radius, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); Pattern fill = BlockTypes.AIR.getDefaultState(); return set(session, context, new SphereBrush()) - .setSize(radiusOpt) + .setSize(radius) .setFill(fill) .setMask(new SingleBlockTypeMask(editSession, BlockTypes.FIRE)); } @@ -679,15 +684,15 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.gravity") public BrushSettings gravityBrush(Player player, LocalSession session, @Arg(desc = "The radius to apply gravity in", def = "5") - Expression radiusOpt, + Expression radius, @Switch(name = 'h', desc = "Affect blocks starting at max Y, rather than the target location Y + radius") boolean fromMaxY, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); return set(session, context, new GravityBrush(fromMaxY)) - .setSize(radiusOpt); + .setSize(radius); } @Command( @@ -702,8 +707,8 @@ public class BrushCommands { "Snow Pic: https://i.imgur.com/Hrzn0I4.png" ) @CommandPermissions("worldedit.brush.height") - public BrushSettings heightBrush(Player player, LocalSession session, @Arg(name = "radius", desc = "Expression", def = "5") Expression radiusOpt, @Arg(name = "image", desc = "String", def = "") String image, @Arg(def = "0", desc = "rotation") @Range(min = 0, max = 360) int rotation, @Arg(name = "yscale", desc = "double", def = "1") double yscale, @Switch(name = 'r', desc = "TODO") boolean randomRotate, @Switch(name = 'l', desc = "TODO") boolean layers, @Switch(name = 's', desc = "TODO") boolean dontSmooth, InjectedValueAccess context) throws WorldEditException, FileNotFoundException { - return terrainBrush(player, session, radiusOpt, image, rotation, yscale, false, randomRotate, layers, !dontSmooth, ScalableHeightMap.Shape.CONE, context); + public BrushSettings heightBrush(Player player, LocalSession session, @Arg(name = "radius", desc = "Expression", def = "5") Expression radius, @Arg(name = "image", desc = "String", def = "") String image, @Arg(def = "0", desc = "rotation") @Range(min = 0, max = 360) int rotation, @Arg(name = "yscale", desc = "double", def = "1") double yscale, @Switch(name = 'r', desc = "TODO") boolean randomRotate, @Switch(name = 'l', desc = "TODO") boolean layers, @Switch(name = 's', desc = "TODO") boolean dontSmooth, InjectedValueAccess context) throws WorldEditException, FileNotFoundException { + return terrainBrush(player, session, radius, image, rotation, yscale, false, randomRotate, layers, !dontSmooth, ScalableHeightMap.Shape.CONE, context); } @Command( @@ -715,7 +720,7 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.height") public BrushSettings cliffBrush(Player player, LocalSession session, @Arg(name = "radius", desc = "Expression", def = "5") - Expression radiusOpt, + Expression radius, @Arg(name = "image", desc = "String", def = "") String image, @Arg(def = "0", desc = "rotation") @Step(90) @Range(min = 0, max = 360) @@ -728,7 +733,7 @@ public class BrushCommands { boolean layers, @Switch(name = 's', desc = "Disables smoothing") boolean dontSmooth, InjectedValueAccess context) throws WorldEditException, FileNotFoundException { - return terrainBrush(player, session, radiusOpt, image, rotation, yscale, true, randomRotate, layers, !dontSmooth, ScalableHeightMap.Shape.CYLINDER, context); + return terrainBrush(player, session, radius, image, rotation, yscale, true, randomRotate, layers, !dontSmooth, ScalableHeightMap.Shape.CYLINDER, context); } @Command( @@ -737,14 +742,14 @@ public class BrushCommands { desc = "This brush raises or lowers land towards the clicked point" ) @CommandPermissions("worldedit.brush.height") - public BrushSettings flattenBrush(Player player, LocalSession session, @Arg(name = "radius", desc = "Expression", def = "5") Expression radiusOpt, @Arg(name = "image", desc = "String", def = "") String image, @Arg(def = "0", desc = "rotation") @Step(90) @Range(min = 0, max = 360) int rotation, @Arg(name = "yscale", desc = "double", def = "1") double yscale, + public BrushSettings flattenBrush(Player player, LocalSession session, @Arg(name = "radius", desc = "Expression", def = "5") Expression radius, @Arg(name = "image", desc = "String", def = "") String image, @Arg(def = "0", desc = "rotation") @Step(90) @Range(min = 0, max = 360) int rotation, @Arg(name = "yscale", desc = "double", def = "1") double yscale, @Switch(name = 'r', desc = "Enables random off-axis rotation") boolean randomRotate, @Switch(name = 'l', desc = "Will work on snow layers") boolean layers, @Switch(name = 's', desc = "Disables smoothing") boolean dontSmooth, InjectedValueAccess context) throws WorldEditException, FileNotFoundException { - return terrainBrush(player, session, radiusOpt, image, rotation, yscale, true, randomRotate, layers, !dontSmooth, ScalableHeightMap.Shape.CONE, context); + return terrainBrush(player, session, radius, image, rotation, yscale, true, randomRotate, layers, !dontSmooth, ScalableHeightMap.Shape.CONE, context); } private BrushSettings terrainBrush(Player player, LocalSession session, @Arg(name = "radius", desc = "Expression") Expression radius, String image, int rotation, double yscale, boolean flat, boolean randomRotate, boolean layers, boolean smooth, ScalableHeightMap.Shape shape, InjectedValueAccess context) throws WorldEditException, FileNotFoundException { @@ -792,13 +797,13 @@ public class BrushCommands { "Video: https://www.youtube.com/watch?v=RPZIaTbqoZw" ) @CommandPermissions("worldedit.brush.copy") - public BrushSettings copy(Player player, LocalSession session, @Arg(name = "radius", desc = "Expression", def = "5") Expression radiusOpt, @Switch(name = 'r', desc = "Apply random rotation on paste") boolean randomRotate, @Switch(name = 'a', desc = "Apply auto view based rotation on paste") boolean autoRotate, InjectedValueAccess context) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); - player.print(BBC.BRUSH_COPY.format(radiusOpt)); + public BrushSettings copy(Player player, LocalSession session, @Arg(name = "radius", desc = "Expression", def = "5") Expression radius, @Switch(name = 'r', desc = "Apply random rotation on paste") boolean randomRotate, @Switch(name = 'a', desc = "Apply auto view based rotation on paste") boolean autoRotate, InjectedValueAccess context) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); + player.print(BBC.BRUSH_COPY.format(radius)); return set(session, context, new CopyPastaBrush(player, session, randomRotate, autoRotate)) - .setSize(radiusOpt); + .setSize(radius); } @Command( @@ -826,7 +831,7 @@ public class BrushCommands { @CommandPermissions("worldedit.brush.butcher") public BrushSettings butcherBrush(Player player, LocalSession session, InjectedValueAccess context, @Arg(desc = "Radius to kill mobs in", def = "5") - Expression radiusOpt, + Expression radius, @Switch(name = 'p', desc = "Also kill pets") boolean killPets, @Switch(name = 'n', desc = "Also kill NPCs") @@ -843,7 +848,7 @@ public class BrushCommands { boolean killFriendly, @Switch(name = 'r', desc = "Also destroy armor stands") boolean killArmorStands) throws WorldEditException { - worldEdit.checkMaxBrushRadius(radiusOpt); + worldEdit.checkMaxBrushRadius(radius); CreatureButcher flags = new CreatureButcher(player); flags.or(CreatureButcher.Flags.FRIENDLY , killFriendly); // No permission check here. Flags will instead be filtered by the subsequent calls. @@ -857,7 +862,7 @@ public class BrushCommands { return set(session, context, new ButcherBrush(flags)) - .setSize(radiusOpt); + .setSize(radius); } public BrushSettings process(CommandLocals locals, BrushSettings settings) throws WorldEditException { @@ -899,12 +904,12 @@ public class BrushCommands { @Arg(desc = "The shape of the region") RegionFactory shape, @Arg(desc = "The size of the brush", def = "5") - Expression radiusOpt, + Expression radius, @Arg(desc = "The density of the brush", def = "20") double density, @Arg(desc = "The type of tree to use") TreeGenerator.TreeType type) throws WorldEditException, EvaluationException { - setOperationBasedBrush(player, localSession, radiusOpt, + setOperationBasedBrush(player, localSession, radius, new Paint(new TreeGeneratorFactory(type), density / 100), shape, "worldedit.brush.forest"); } @@ -917,8 +922,8 @@ public class BrushCommands { @Arg(desc = "The shape of the region") RegionFactory shape, @Arg(desc = "The size of the brush", def = "5") - Expression radiusOpt) throws WorldEditException, EvaluationException { - setOperationBasedBrush(player, localSession, radiusOpt, + Expression radius) throws WorldEditException, EvaluationException { + setOperationBasedBrush(player, localSession, radius, new Deform("y-=1"), shape, "worldedit.brush.raise"); } @@ -931,8 +936,8 @@ public class BrushCommands { @Arg(desc = "The shape of the region") RegionFactory shape, @Arg(desc = "The size of the brush", def = "5") - Expression radiusOpt) throws WorldEditException, EvaluationException { - setOperationBasedBrush(player, localSession, radiusOpt, + Expression radius) throws WorldEditException, EvaluationException { + setOperationBasedBrush(player, localSession, radius, new Deform("y+=1"), shape, "worldedit.brush.lower"); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java index 75075133c..41d6a5abf 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java @@ -62,6 +62,7 @@ import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.internal.annotation.ClipboardMask; import com.sk89q.worldedit.internal.annotation.Direction; import com.sk89q.worldedit.internal.annotation.Selection; import com.sk89q.worldedit.math.BlockVector3; @@ -441,6 +442,7 @@ public class ClipboardCommands { @Switch(name = 'b', desc = "Paste biomes if available") boolean pasteBiomes, @ArgFlag(name = 'm', desc = "Only paste blocks matching this mask", def = "") + @ClipboardMask Mask sourceMask) throws WorldEditException { ClipboardHolder holder = session.getClipboard(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/FactoryConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/FactoryConverter.java index d6259abf3..8b5a56f0c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/FactoryConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/FactoryConverter.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.command.argument; +import com.sk89q.worldedit.EmptyClipboardException; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.blocks.BaseItem; @@ -27,9 +28,14 @@ import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.transform.BlockTransformExtent; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.internal.annotation.ClipboardMask; import com.sk89q.worldedit.internal.registry.AbstractFactory; +import com.sk89q.worldedit.math.transform.Transform; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.session.request.RequestExtent; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.World; @@ -41,30 +47,53 @@ import org.enginehub.piston.converter.SuccessfulConversion; import org.enginehub.piston.inject.InjectedValueAccess; import org.enginehub.piston.inject.Key; +import javax.annotation.Nullable; import java.util.List; +import java.util.function.Consumer; import java.util.function.Function; public class FactoryConverter implements ArgumentConverter { public static void register(WorldEdit worldEdit, CommandManager commandManager) { commandManager.registerConverter(Key.of(Pattern.class), - new FactoryConverter<>(worldEdit, WorldEdit::getPatternFactory, "pattern")); + new FactoryConverter<>(worldEdit, WorldEdit::getPatternFactory, "pattern", null)); commandManager.registerConverter(Key.of(Mask.class), - new FactoryConverter<>(worldEdit, WorldEdit::getMaskFactory, "mask")); + new FactoryConverter<>(worldEdit, WorldEdit::getMaskFactory, "mask", null)); commandManager.registerConverter(Key.of(BaseItem.class), - new FactoryConverter<>(worldEdit, WorldEdit::getItemFactory, "item")); + new FactoryConverter<>(worldEdit, WorldEdit::getItemFactory, "item", null)); + + commandManager.registerConverter(Key.of(Mask.class, ClipboardMask.class), + new FactoryConverter<>(worldEdit, WorldEdit::getMaskFactory, "mask", + context -> { + try { + ClipboardHolder holder = context.getSession().getClipboard(); + Transform transform = holder.getTransform(); + Extent target; + if (transform.isIdentity()) { + target = holder.getClipboard(); + } else { + target = new BlockTransformExtent(holder.getClipboard(), transform); + } + context.setExtent(target); + } catch (EmptyClipboardException e) { + throw new IllegalStateException(e); + } + })); } private final WorldEdit worldEdit; private final Function> factoryExtractor; private final String description; + @Nullable private final Consumer contextTweaker; private FactoryConverter(WorldEdit worldEdit, Function> factoryExtractor, - String description) { + String description, + @Nullable Consumer contextTweaker) { this.worldEdit = worldEdit; this.factoryExtractor = factoryExtractor; this.description = description; + this.contextTweaker = contextTweaker; } @Override @@ -80,10 +109,15 @@ public class FactoryConverter implements ArgumentConverter { if (extent instanceof World) { parserContext.setWorld((World) extent); } + parserContext.setExtent(new RequestExtent()); } parserContext.setSession(session); parserContext.setRestricted(true); + if (contextTweaker != null) { + contextTweaker.accept(parserContext); + } + try { return SuccessfulConversion.fromSingle( factoryExtractor.apply(worldEdit).parseFromInput(argument, parserContext) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BiomeMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BiomeMaskParser.java index 9e38e2e82..21fc46c4f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BiomeMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BiomeMaskParser.java @@ -78,6 +78,6 @@ public class BiomeMaskParser extends InputParser { biomes.add(biome); } - return Masks.asMask(new BiomeMask2D(new RequestExtent(), biomes)); + return Masks.asMask(new BiomeMask2D(context.getExtent(), biomes)); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockCategoryMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockCategoryMaskParser.java index ee7bebf44..5001d2c38 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockCategoryMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockCategoryMaskParser.java @@ -54,7 +54,7 @@ public class BlockCategoryMaskParser extends InputParser { if (category == null) { throw new InputParseException("Unrecognised tag '" + input.substring(2) + '\''); } else { - return new BlockCategoryMask(new RequestExtent(), category); + return new BlockCategoryMask(context.getExtent(), category); } } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java index 95884b821..026aa712f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java @@ -53,7 +53,7 @@ public class BlockStateMaskParser extends InputParser { boolean strict = input.charAt(1) == '='; String states = input.substring(2 + (strict ? 1 : 0), input.length() - 1); try { - return new BlockStateMask(new RequestExtent(), + return new BlockStateMask(context.getExtent(), Splitter.on(',').omitEmptyStrings().trimResults().withKeyValueSeparator('=').split(states), strict); } catch (Exception e) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlocksMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlocksMaskParser.java index 8580eb8a1..1a8b6e0b7 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlocksMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlocksMaskParser.java @@ -56,7 +56,7 @@ public class BlocksMaskParser extends InputParser { if (holders.isEmpty()) { return null; } - return new BlockMask(new RequestExtent(), holders); + return new BlockMask(context.getExtent(), holders); } catch (NoMatchException e) { return null; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExistingMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExistingMaskParser.java index 21feb6449..324bf0857 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExistingMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExistingMaskParser.java @@ -44,6 +44,6 @@ public class ExistingMaskParser extends SimpleInputParser { @Override public Mask parseFromSimpleInput(String input, ParserContext context) { - return new ExistingBlockMask(new RequestExtent()); + return new ExistingBlockMask(context.getExtent()); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java index a7d55e990..ed05f9b62 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/ExpressionMaskParser.java @@ -58,7 +58,7 @@ public class ExpressionMaskParser extends InputParser { try { Expression exp = Expression.compile(input.substring(1), "x", "y", "z"); WorldEditExpressionEnvironment env = new WorldEditExpressionEnvironment( - new RequestExtent(), Vector3.ONE, Vector3.ZERO); + context.getExtent(), Vector3.ONE, Vector3.ZERO); exp.setEnvironment(env); if (context.getActor() != null) { SessionOwner owner = context.getActor(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java index ede9b6c79..fa49aea32 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/OffsetMaskParser.java @@ -62,7 +62,7 @@ public class OffsetMaskParser extends InputParser { if (input.length() > 1) { submask = worldEdit.getMaskFactory().parseFromInput(input.substring(1), context); } else { - submask = new ExistingBlockMask(new RequestExtent()); + submask = new ExistingBlockMask(context.getExtent()); } OffsetMask offsetMask = new OffsetMask(submask, BlockVector3.at(0, firstChar == '>' ? -1 : 1, 0)); return new MaskIntersection(offsetMask, Masks.negate(submask)); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SolidMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SolidMaskParser.java index f5ae3244a..4fafcfaea 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SolidMaskParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/SolidMaskParser.java @@ -44,6 +44,6 @@ public class SolidMaskParser extends SimpleInputParser { @Override public Mask parseFromSimpleInput(String input, ParserContext context) { - return new SolidBlockMask(new RequestExtent()); + return new SolidBlockMask(context.getExtent()); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java index 112708924..bf98dc45a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/ChunkBatchingExtent.java @@ -19,8 +19,7 @@ package com.sk89q.worldedit.extent.reorder; -import com.google.common.collect.Table; -import com.google.common.collect.TreeBasedTable; +import com.google.common.collect.ImmutableSortedSet; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.extent.AbstractBufferingExtent; import com.sk89q.worldedit.extent.Extent; @@ -28,15 +27,14 @@ import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.RunContext; import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.RegionOptimizedComparator; +import com.sk89q.worldedit.util.collection.BlockMap; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockStateHolder; -import java.util.Comparator; -import java.util.HashSet; + import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.Set; /** * A special extent that batches changes into Minecraft chunks. This helps @@ -46,17 +44,7 @@ import java.util.Set; */ public class ChunkBatchingExtent extends AbstractBufferingExtent { - /** - * Comparator optimized for sorting chunks by the region file they reside - * in. This allows for file caches to be used while loading the chunk. - */ - private static final Comparator REGION_OPTIMIZED_SORT = - Comparator.comparing((BlockVector2 vec) -> vec.shr(5), BlockVector2.COMPARING_GRID_ARRANGEMENT) - .thenComparing(BlockVector2.COMPARING_GRID_ARRANGEMENT); - - private final Table batches = - TreeBasedTable.create(REGION_OPTIMIZED_SORT, BlockVector3.sortByCoordsYzx()); - private final Set containedBlocks = new HashSet<>(); + private final BlockMap blockMap = BlockMap.create(); private boolean enabled; public ChunkBatchingExtent(Extent extent) { @@ -80,32 +68,20 @@ public class ChunkBatchingExtent extends AbstractBufferingExtent { return enabled; } - private BlockVector2 getChunkPos(BlockVector3 location) { - return location.shr(4).toBlockVector2(); - } - - private BlockVector3 getInChunkPos(BlockVector3 location) { - return BlockVector3.at(location.getX() & 15, location.getY(), location.getZ() & 15); - } @Override public > boolean setBlock(BlockVector3 location, B block) throws WorldEditException { if (!enabled) { return setDelegateBlock(location, block); } - BlockVector2 chunkPos = getChunkPos(location); - BlockVector3 inChunkPos = getInChunkPos(location); - batches.put(chunkPos, inChunkPos, block.toBaseBlock()); - containedBlocks.add(location); + blockMap.put(location, block.toBaseBlock()); return true; } @Override protected Optional getBufferedBlock(BlockVector3 position) { - if (!containedBlocks.contains(position)) { - return Optional.empty(); - } - return Optional.of(batches.get(getChunkPos(position), getInChunkPos(position))); + return Optional.ofNullable(blockMap.get(position)); } + @Override protected Operation commitBefore() { if (!commitRequired()) { @@ -114,24 +90,21 @@ public class ChunkBatchingExtent extends AbstractBufferingExtent { return new Operation() { // we get modified between create/resume -- only create this on resume to prevent CME - private Iterator>> batchIterator; + private Iterator iterator; @Override public Operation resume(RunContext run) throws WorldEditException { - if (batchIterator == null) { - batchIterator = batches.rowMap().entrySet().iterator(); + if (iterator == null) { + iterator = ImmutableSortedSet.copyOf(RegionOptimizedComparator.INSTANCE, + blockMap.keySet()).iterator(); } - if (!batchIterator.hasNext()) { - return null; + while (iterator.hasNext()) { + BlockVector3 position = iterator.next(); + BaseBlock block = blockMap.get(position); + getExtent().setBlock(position, block); } - Map.Entry> next = batchIterator.next(); - BlockVector3 chunkOffset = next.getKey().toBlockVector3().shl(4); - for (Map.Entry block : next.getValue().entrySet()) { - getExtent().setBlock(block.getKey().add(chunkOffset), block.getValue()); - containedBlocks.remove(block.getKey()); - } - batchIterator.remove(); - return this; + blockMap.clear(); + return null; } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java index e650bf90a..733d6c7de 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/reorder/MultiStageReorder.java @@ -24,23 +24,23 @@ import com.sk89q.worldedit.extent.AbstractBufferingExtent; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.OperationQueue; -import com.sk89q.worldedit.function.operation.SetLocatedBlocks; +import com.sk89q.worldedit.function.operation.RunContext; +import com.sk89q.worldedit.function.operation.SetBlockMap; import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.util.collection.LocatedBlockList; +import com.sk89q.worldedit.util.collection.BlockMap; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockCategories; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; + import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; /** * Re-orders blocks into several stages. @@ -142,8 +142,7 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde priorityMap.put(BlockTypes.MOVING_PISTON, PlacementPriority.FINAL); } - private final Set containedBlocks = new HashSet<>(); - private Map stages = new HashMap<>(); + private Map stages = new HashMap<>(); private boolean enabled; @@ -177,7 +176,7 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde this.enabled = enabled; for (PlacementPriority priority : PlacementPriority.values()) { - stages.put(priority, new LocatedBlockList()); + stages.put(priority, BlockMap.create()); } } @@ -219,7 +218,7 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde return setDelegateBlock(location, block); } - BlockState existing = getBlock(location); + BlockState existing = getExtent().getBlock(location); PlacementPriority priority = getPlacementPriority(block); PlacementPriority srcPriority = getPlacementPriority(existing); @@ -228,13 +227,13 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde switch (srcPriority) { case FINAL: - stages.get(PlacementPriority.CLEAR_FINAL).add(location, replacement); + stages.get(PlacementPriority.CLEAR_FINAL).put(location, replacement); break; case LATE: - stages.get(PlacementPriority.CLEAR_LATE).add(location, replacement); + stages.get(PlacementPriority.CLEAR_LATE).put(location, replacement); break; case LAST: - stages.get(PlacementPriority.CLEAR_LAST).add(location, replacement); + stages.get(PlacementPriority.CLEAR_LAST).put(location, replacement); break; } @@ -243,16 +242,12 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde } } - stages.get(priority).add(location, block); - containedBlocks.add(location); + stages.get(priority).put(location, block.toBaseBlock()); return !existing.equalsFuzzy(block); } @Override protected Optional getBufferedBlock(BlockVector3 position) { - if (!containedBlocks.contains(position)) { - return Optional.empty(); - } return stages.values().stream() .map(blocks -> blocks.get(position)) .filter(Objects::nonNull) @@ -266,7 +261,17 @@ public class MultiStageReorder extends AbstractBufferingExtent implements Reorde } List operations = new ArrayList<>(); for (PlacementPriority priority : PlacementPriority.values()) { - operations.add(new SetLocatedBlocks(getExtent(), stages.get(priority))); + BlockMap blocks = stages.get(priority); + operations.add(new SetBlockMap(getExtent(), blocks) { + @Override + public Operation resume(RunContext run) throws WorldEditException { + Operation operation = super.resume(run); + if (operation == null) { + blocks.clear(); + } + return operation; + } + }); } return new OperationQueue(operations); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/SetBlockMap.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/SetBlockMap.java new file mode 100644 index 000000000..863aadd03 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/SetBlockMap.java @@ -0,0 +1,60 @@ +/* + * 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.function.operation; + +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.LocatedBlock; +import com.sk89q.worldedit.util.collection.BlockMap; +import com.sk89q.worldedit.world.block.BaseBlock; + +import java.util.List; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class SetBlockMap implements Operation { + + private final Extent extent; + private final BlockMap blocks; + + public SetBlockMap(Extent extent, BlockMap blocks) { + this.extent = checkNotNull(extent); + this.blocks = checkNotNull(blocks); + } + + @Override + public Operation resume(RunContext run) throws WorldEditException { + for (Map.Entry entry : blocks.entrySet()) { + extent.setBlock(entry.getKey(), entry.getValue()); + } + return null; + } + + @Override + public void cancel() { + } + + @Override + public void addStatusMessages(List messages) { + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/annotation/ClipboardMask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/annotation/ClipboardMask.java new file mode 100644 index 000000000..6b8be85e0 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/annotation/ClipboardMask.java @@ -0,0 +1,37 @@ +/* + * 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.internal.annotation; + +import com.sk89q.worldedit.function.mask.Mask; +import org.enginehub.piston.inject.InjectAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotates a {@link Mask} parameter to use the clipboard as the extent instead of target World/EditSession. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +@InjectAnnotation +public @interface ClipboardMask { +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/block/BlockStateIdAccess.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/block/BlockStateIdAccess.java new file mode 100644 index 000000000..c62b96225 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/block/BlockStateIdAccess.java @@ -0,0 +1,77 @@ +/* + * 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.internal.block; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.registry.BlockRegistry; + +import javax.annotation.Nullable; +import java.util.BitSet; +import java.util.OptionalInt; + +import static com.google.common.base.Preconditions.checkState; + +public final class BlockStateIdAccess { + + private static final BiMap ASSIGNED_IDS = HashBiMap.create(2 << 13); + + public static OptionalInt getBlockStateId(BlockState holder) { + Integer value = ASSIGNED_IDS.get(holder); + return value == null ? OptionalInt.empty() : OptionalInt.of(value); + } + + public static @Nullable BlockState getBlockStateById(int id) { + return ASSIGNED_IDS.inverse().get(id); + } + + /** + * For platforms that don't have an internal ID system, + * {@link BlockRegistry#getInternalBlockStateId(BlockState)} will return + * {@link OptionalInt#empty()}. In those cases, we will use our own ID system, + * since it's useful for other entries as well. + * @return an unused ID in WorldEdit's ID tracker + */ + private static int provideUnusedWorldEditId() { + return usedIds.nextClearBit(0); + } + + private static final BitSet usedIds = new BitSet(); + + public static void register(BlockState blockState, OptionalInt id) { + int i = id.orElseGet(BlockStateIdAccess::provideUnusedWorldEditId); + BlockState existing = ASSIGNED_IDS.inverse().get(i); + checkState(existing == null || existing == blockState, + "BlockState %s is using the same block ID (%s) as BlockState %s", + blockState, i, existing); + ASSIGNED_IDS.put(blockState, i); + usedIds.set(i); + } + + public static void clear() { + ASSIGNED_IDS.clear(); + usedIds.clear(); + } + + private BlockStateIdAccess() { + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BitMath.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BitMath.java new file mode 100644 index 000000000..46fa66e73 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BitMath.java @@ -0,0 +1,52 @@ +/* + * 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.math; + +public final class BitMath { + + public static int mask(int bits) { + return ~(~0 << bits); + } + + public static int unpackX(long packed) { + return extractSigned(packed, 0, 26); + } + + public static int unpackZ(long packed) { + return extractSigned(packed, 26, 26); + } + + public static int unpackY(long packed) { + return extractSigned(packed, 26 + 26, 12); + } + + public static int extractSigned(long i, int shift, int bits) { + return fixSign((int) (i >> shift) & mask(bits), bits); + } + + public static int fixSign(int i, int bits) { + // Using https://stackoverflow.com/a/29266331/436524 + return i << (32 - bits) >> (32 - bits); + } + + private BitMath() { + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java index 77b5c7ba8..a940da291 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/BlockVector3.java @@ -20,6 +20,10 @@ package com.sk89q.worldedit.math; import static com.google.common.base.Preconditions.checkArgument; +import static com.sk89q.worldedit.math.BitMath.mask; +import static com.sk89q.worldedit.math.BitMath.unpackX; +import static com.sk89q.worldedit.math.BitMath.unpackY; +import static com.sk89q.worldedit.math.BitMath.unpackZ; import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.extent.Extent; @@ -67,6 +71,31 @@ public abstract class BlockVector3 { return new BlockVector3Imp(x, y, z); } + private static final int WORLD_XZ_MINMAX = 30_000_000; + private static final int WORLD_Y_MAX = 4095; + + private static boolean isHorizontallyInBounds(int h) { + return -WORLD_XZ_MINMAX <= h && h <= WORLD_XZ_MINMAX; + } + + public static boolean isLongPackable(BlockVector3 location) { + return isHorizontallyInBounds(location.getX()) && + isHorizontallyInBounds(location.getZ()) && + 0 <= location.getY() && location.getY() <= WORLD_Y_MAX; + } + + public static void checkLongPackable(BlockVector3 location) { + checkArgument(isLongPackable(location), + "Location exceeds long packing limits: %s", location); + } + + private static final long BITS_26 = mask(26); + private static final long BITS_12 = mask(12); + + public static BlockVector3 fromLongPackedForm(long packed) { + return at(unpackX(packed), unpackY(packed), unpackZ(packed)); + } + // thread-safe initialization idiom private static final class YzxOrderComparator { @@ -154,6 +183,11 @@ public abstract class BlockVector3 { // return orDefault.setComponents(getX(), getY() - 1, getZ()); // } + public long toLongPackedForm() { + checkLongPackable(this); + return (getX() & BITS_26) | ((getZ() & BITS_26) << 26) | (((getY() & (long) BITS_12) << (26 + 26))); + } + /** * Get the X coordinate. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/RegionOptimizedChunkComparator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/RegionOptimizedChunkComparator.java new file mode 100644 index 000000000..d6d2d89d6 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/RegionOptimizedChunkComparator.java @@ -0,0 +1,41 @@ +/* + * 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.math; + +import java.util.Comparator; + +import static com.sk89q.worldedit.math.BlockVector2.COMPARING_GRID_ARRANGEMENT; + +/** + * Sort block positions by region, then chunk. + */ +public class RegionOptimizedChunkComparator { + + private static final Comparator CHUNK_COMPARATOR = + Comparator.comparing((BlockVector2 chunkPos) -> chunkPos.shr(5), COMPARING_GRID_ARRANGEMENT) + .thenComparing(COMPARING_GRID_ARRANGEMENT); + + public static final Comparator INSTANCE + = Comparator.comparing(blockPos -> blockPos.toBlockVector2().shr(4), CHUNK_COMPARATOR); + + private RegionOptimizedChunkComparator() { + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/RegionOptimizedComparator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/RegionOptimizedComparator.java new file mode 100644 index 000000000..ed715d703 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/RegionOptimizedComparator.java @@ -0,0 +1,36 @@ +/* + * 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.math; + +import java.util.Comparator; + +/** + * Sort block positions by region, chunk, and finally Y-Z-X. + */ +public class RegionOptimizedComparator { + + public static final Comparator INSTANCE + = RegionOptimizedChunkComparator.INSTANCE + .thenComparing(BlockVector3.sortByCoordsYzx()); + + private RegionOptimizedComparator() { + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java index 4a5a5cae6..0e98ee48c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/PropertiesConfiguration.java @@ -121,6 +121,7 @@ public class PropertiesConfiguration extends LocalConfiguration { butcherMaxRadius = getInt("butcher-max-radius", butcherMaxRadius); allowSymlinks = getBool("allow-symbolic-links", allowSymlinks); serverSideCUI = getBool("server-side-cui", serverSideCUI); + extendedYLimit = getBool("extended-y-limit", extendedYLimit); LocalSession.MAX_HISTORY_SIZE = Math.max(15, getInt("history-size", 15)); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java index 2af093290..c65157fb1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/YAMLConfiguration.java @@ -125,6 +125,7 @@ public class YAMLConfiguration extends LocalConfiguration { String type = config.getString("shell-save-type", "").trim(); shellSaveType = type.isEmpty() ? null : type; + extendedYLimit = config.getBoolean("compat.extended-y-limit", false); } public void unload() { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/BlockMap.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/BlockMap.java new file mode 100644 index 000000000..2620a906e --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/BlockMap.java @@ -0,0 +1,427 @@ +/* + * 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.util.collection; + +import com.google.common.collect.AbstractIterator; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BaseBlock; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIterator; + +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +import static com.sk89q.worldedit.math.BitMath.fixSign; +import static com.sk89q.worldedit.math.BitMath.mask; + +/** + * A space-efficient map implementation for block locations. + */ +public class BlockMap extends AbstractMap { + + /* ========================= + IF YOU MAKE CHANGES TO THIS CLASS + Re-run BlockMapTest with the blockmap.fulltesting=true system property. + Or just temporarily remove the annotation disabling the related tests. + ========================= */ + + public static BlockMap create() { + return new BlockMap(); + } + + public static BlockMap copyOf(Map source) { + return new BlockMap(source); + } + + /* + * Stores blocks by sub-dividing them into smaller groups. + * A block location is 26 bits long for x + z, and usually + * 8 bits for y, although mods such as cubic chunks may + * expand this to infinite. We support up to 32 bits of y. + * + * Grouping key stores 20 bits x + z, 24 bits y. + * Inner key stores 6 bits x + z, 8 bits y. + * Order (lowest to highest) is x-z-y. + */ + + private static final long BITS_24 = mask(24); + private static final long BITS_20 = mask(20); + private static final int BITS_8 = mask(8); + private static final int BITS_6 = mask(6); + + private static long toGroupKey(BlockVector3 location) { + return ((location.getX() >>> 6) & BITS_20) + | (((location.getZ() >>> 6) & BITS_20) << 20) + | (((location.getY() >>> 8) & BITS_24) << (20 + 20)); + } + + private static int toInnerKey(BlockVector3 location) { + return (location.getX() & BITS_6) + | ((location.getZ() & BITS_6) << 6) + | ((location.getY() & BITS_8) << (6 + 6)); + } + + private static final long GROUP_X = BITS_20; + private static final long GROUP_Z = BITS_20 << 20; + private static final long GROUP_Y = BITS_24 << (20 + 20); + private static final int INNER_X = BITS_6; + private static final int INNER_Z = BITS_6 << 6; + private static final int INNER_Y = BITS_8 << (6 + 6); + + private static BlockVector3 reconstructLocation(long group, int inner) { + int groupX = (int) ((group & GROUP_X) << 6); + int x = fixSign(groupX | (inner & INNER_X), 26); + int groupZ = (int) ((group & GROUP_Z) >>> (20 - 6)); + int z = fixSign(groupZ | ((inner & INNER_Z) >>> 6), 26); + int groupY = (int) ((group & GROUP_Y) >>> (20 + 20 - 8)); + int y = groupY | ((inner & INNER_Y) >>> (6 + 6)); + return BlockVector3.at(x, y, z); + } + + private final Long2ObjectMap maps = new Long2ObjectOpenHashMap<>(4, 1f); + private Set> entrySet; + private Collection values; + + private BlockMap() { + } + + private BlockMap(Map source) { + putAll(source); + } + + private SubBlockMap getOrCreateMap(long groupKey) { + return maps.computeIfAbsent(groupKey, k -> new SubBlockMap()); + } + + private SubBlockMap getOrEmptyMap(long groupKey) { + return maps.getOrDefault(groupKey, SubBlockMap.EMPTY); + } + + /** + * Apply the function the the map at {@code groupKey}, and if the function empties the map, + * delete it from {@code maps}. + */ + private R cleanlyModifyMap(long groupKey, Function, R> func) { + SubBlockMap map = maps.get(groupKey); + if (map != null) { + R result = func.apply(map); + if (map.isEmpty()) { + maps.remove(groupKey); + } + return result; + } + map = new SubBlockMap(); + R result = func.apply(map); + if (!map.isEmpty()) { + maps.put(groupKey, map); + } + return result; + } + + @Override + public BaseBlock put(BlockVector3 key, BaseBlock value) { + return getOrCreateMap(toGroupKey(key)).put(toInnerKey(key), value); + } + + @Override + public BaseBlock getOrDefault(Object key, BaseBlock defaultValue) { + BlockVector3 vec = (BlockVector3) key; + return getOrEmptyMap(toGroupKey(vec)) + .getOrDefault(toInnerKey(vec), defaultValue); + } + + @Override + public void forEach(BiConsumer action) { + maps.forEach((groupKey, m) -> + m.forEach((innerKey, block) -> + action.accept(reconstructLocation(groupKey, innerKey), block) + ) + ); + } + + @Override + public void replaceAll(BiFunction function) { + maps.forEach((groupKey, m) -> + m.replaceAll((innerKey, block) -> + function.apply(reconstructLocation(groupKey, innerKey), block) + ) + ); + } + + @Override + public BaseBlock putIfAbsent(BlockVector3 key, BaseBlock value) { + return getOrCreateMap(toGroupKey(key)).putIfAbsent(toInnerKey(key), value); + } + + @Override + public boolean remove(Object key, Object value) { + BlockVector3 vec = (BlockVector3) key; + return cleanlyModifyMap(toGroupKey(vec), + map -> map.remove(toInnerKey(vec), value)); + } + + @Override + public boolean replace(BlockVector3 key, BaseBlock oldValue, BaseBlock newValue) { + return cleanlyModifyMap(toGroupKey(key), + map -> map.replace(toInnerKey(key), oldValue, newValue)); + } + + @Override + public BaseBlock replace(BlockVector3 key, BaseBlock value) { + return getOrCreateMap(toGroupKey(key)).replace(toInnerKey(key), value); + } + + @Override + public BaseBlock computeIfAbsent(BlockVector3 key, Function mappingFunction) { + return cleanlyModifyMap(toGroupKey(key), + map -> map.computeIfAbsent(toInnerKey(key), ik -> mappingFunction.apply(key))); + } + + @Override + public BaseBlock computeIfPresent(BlockVector3 key, BiFunction remappingFunction) { + return cleanlyModifyMap(toGroupKey(key), + map -> map.computeIfPresent(toInnerKey(key), (ik, block) -> remappingFunction.apply(key, block))); + } + + @Override + public BaseBlock compute(BlockVector3 key, BiFunction remappingFunction) { + return cleanlyModifyMap(toGroupKey(key), + map -> map.compute(toInnerKey(key), (ik, block) -> remappingFunction.apply(key, block))); + } + + @Override + public BaseBlock merge(BlockVector3 key, BaseBlock value, BiFunction remappingFunction) { + return cleanlyModifyMap(toGroupKey(key), + map -> map.merge(toInnerKey(key), value, remappingFunction)); + } + + @Override + public Set> entrySet() { + Set> es = entrySet; + if (es == null) { + entrySet = es = new AbstractSet>() { + @Override + public Iterator> iterator() { + return new AbstractIterator>() { + + private final ObjectIterator> primaryIterator + = Long2ObjectMaps.fastIterator(maps); + private long currentGroupKey; + private ObjectIterator> secondaryIterator; + + @Override + protected Entry computeNext() { + if (secondaryIterator == null || !secondaryIterator.hasNext()) { + if (!primaryIterator.hasNext()) { + return endOfData(); + } + + Long2ObjectMap.Entry next = primaryIterator.next(); + currentGroupKey = next.getLongKey(); + secondaryIterator = Int2ObjectMaps.fastIterator(next.getValue()); + } + Int2ObjectMap.Entry next = secondaryIterator.next(); + return new LazyEntry(currentGroupKey, next.getIntKey(), next.getValue()); + } + }; + } + + @Override + public int size() { + return BlockMap.this.size(); + } + }; + } + return es; + } + + private final class LazyEntry implements Map.Entry { + + private final long groupKey; + private final int innerKey; + private BlockVector3 lazyKey; + private BaseBlock value; + + private LazyEntry(long groupKey, int innerKey, BaseBlock value) { + this.groupKey = groupKey; + this.innerKey = innerKey; + this.value = value; + } + + @Override + public BlockVector3 getKey() { + BlockVector3 result = lazyKey; + if (result == null) { + lazyKey = result = reconstructLocation(groupKey, innerKey); + } + return result; + } + + @Override + public BaseBlock getValue() { + return value; + } + + @Override + public BaseBlock setValue(BaseBlock value) { + this.value = value; + return getOrCreateMap(groupKey).put(innerKey, value); + } + + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + if (o instanceof LazyEntry) { + LazyEntry otherE = (LazyEntry) o; + return otherE.groupKey == groupKey + && otherE.innerKey == innerKey + && Objects.equals(value, e.getValue()); + } + return Objects.equals(getKey(), e.getKey()) && Objects.equals(value, e.getValue()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getKey()) ^ Objects.hashCode(value); + } + + @Override + public String toString() { + return getKey() + "=" + getValue(); + } + } + + @Override + public boolean containsValue(Object value) { + return maps.values().stream().anyMatch(m -> m.containsValue(value)); + } + + @Override + public boolean containsKey(Object key) { + BlockVector3 vec = (BlockVector3) key; + Map activeMap = maps.get(toGroupKey(vec)); + if (activeMap == null) { + return false; + } + return activeMap.containsKey(toInnerKey(vec)); + } + + @Override + public BaseBlock get(Object key) { + BlockVector3 vec = (BlockVector3) key; + Map activeMap = maps.get(toGroupKey(vec)); + if (activeMap == null) { + return null; + } + return activeMap.get(toInnerKey(vec)); + } + + @Override + public BaseBlock remove(Object key) { + BlockVector3 vec = (BlockVector3) key; + Map activeMap = maps.get(toGroupKey(vec)); + if (activeMap == null) { + return null; + } + BaseBlock removed = activeMap.remove(toInnerKey(vec)); + if (activeMap.isEmpty()) { + maps.remove(toGroupKey(vec)); + } + return removed; + } + + @Override + public void putAll(Map m) { + if (m instanceof BlockMap) { + // optimize insertions: + ((BlockMap) m).maps.forEach((groupKey, map) -> + getOrCreateMap(groupKey).putAll(map) + ); + } else { + super.putAll(m); + } + } + + @Override + public void clear() { + maps.clear(); + } + + @Override + public int size() { + return maps.values().stream().mapToInt(Map::size).sum(); + } + + // no keySet override, since we can't really optimize it. + // we can optimize values access though, by skipping BV construction. + + @Override + public Collection values() { + Collection vs = values; + if (vs == null) { + values = vs = new AbstractCollection() { + @Override + public Iterator iterator() { + return maps.values().stream() + .flatMap(m -> m.values().stream()) + .iterator(); + } + + @Override + public int size() { + return BlockMap.this.size(); + } + }; + } + return vs; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof BlockMap) { + // optimize by skipping entry translations: + return maps.equals(((BlockMap) o).maps); + } + return super.equals(o); + } + + // satisfy checkstyle + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LocatedBlockList.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LocatedBlockList.java index c1296e88d..221c3bf5a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LocatedBlockList.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LocatedBlockList.java @@ -19,71 +19,76 @@ package com.sk89q.worldedit.util.collection; -import static com.google.common.base.Preconditions.checkNotNull; - +import com.google.common.collect.Iterators; +import com.sk89q.worldedit.WorldEdit; +import com.google.common.collect.Iterators; +import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.LocatedBlock; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockStateHolder; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; + import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Iterator; + +import static com.google.common.base.Preconditions.checkNotNull; /** * Wrapper around a list of blocks located in the world. */ public class LocatedBlockList implements Iterable { - private final Map map = new LinkedHashMap<>(); + private final BlockMap blocks = BlockMap.create(); + private final PositionList order = PositionList.create( + WorldEdit.getInstance().getConfiguration().extendedYLimit + ); public LocatedBlockList() { } public LocatedBlockList(Collection collection) { for (LocatedBlock locatedBlock : collection) { - map.put(locatedBlock.getLocation(), locatedBlock); + add(locatedBlock.getLocation(), locatedBlock.getBlock()); } } public void add(LocatedBlock setBlockCall) { checkNotNull(setBlockCall); - map.put(setBlockCall.getLocation(), setBlockCall); + add(setBlockCall.getLocation(), setBlockCall.getBlock()); } public > void add(BlockVector3 location, B block) { - add(new LocatedBlock(location, block.toBaseBlock())); + blocks.put(location, block.toBaseBlock()); + order.add(location); } public boolean containsLocation(BlockVector3 location) { - return map.containsKey(location); + return blocks.containsKey(location); } public @Nullable BaseBlock get(BlockVector3 location) { - return map.get(location).getBlock(); + return blocks.get(location); } public int size() { - return map.size(); + return order.size(); } public void clear() { - map.clear(); + blocks.clear(); + order.clear(); } @Override public Iterator iterator() { - return map.values().iterator(); + return Iterators.transform(order.iterator(), position -> + new LocatedBlock(position, blocks.get(position))); } public Iterator reverseIterator() { - List data = new ArrayList<>(map.values()); - Collections.reverse(data); - return data.iterator(); + return Iterators.transform(order.reverseIterator(), position -> + new LocatedBlock(position, blocks.get(position))); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LongPositionList.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LongPositionList.java new file mode 100644 index 000000000..8d5c0b72a --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/LongPositionList.java @@ -0,0 +1,91 @@ +/* + * 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.util.collection; + +import com.google.common.collect.AbstractIterator; +import com.sk89q.worldedit.math.BlockVector3; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import it.unimi.dsi.fastutil.longs.LongListIterator; + +import java.util.Iterator; +import java.util.function.Predicate; +import java.util.function.ToLongFunction; + +class LongPositionList implements PositionList { + + private final LongList delegate = new LongArrayList(); + + @Override + public BlockVector3 get(int index) { + return BlockVector3.fromLongPackedForm(delegate.getLong(index)); + } + + @Override + public void add(BlockVector3 vector) { + delegate.add(vector.toLongPackedForm()); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public Iterator iterator() { + return new PositionIterator(delegate.iterator(), + LongListIterator::hasNext, + LongListIterator::nextLong); + } + + @Override + public Iterator reverseIterator() { + return new PositionIterator(delegate.listIterator(size()), + LongListIterator::hasPrevious, + LongListIterator::previousLong); + } + + private static final class PositionIterator extends AbstractIterator { + + private final LongListIterator iterator; + private final Predicate hasNext; + private final ToLongFunction next; + + private PositionIterator(LongListIterator iterator, + Predicate hasNext, + ToLongFunction next) { + this.iterator = iterator; + this.hasNext = hasNext; + this.next = next; + } + + @Override + protected BlockVector3 computeNext() { + return hasNext.test(iterator) + ? BlockVector3.fromLongPackedForm(next.applyAsLong(iterator)) + : endOfData(); + } + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/PositionList.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/PositionList.java new file mode 100644 index 000000000..793f0fef3 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/PositionList.java @@ -0,0 +1,47 @@ +/* + * 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.util.collection; + +import com.sk89q.worldedit.math.BlockVector3; + +import java.util.Iterator; + +interface PositionList { + + static PositionList create(boolean extendedYLimit) { + if (extendedYLimit) { + return new VectorPositionList(); + } + return new LongPositionList(); + } + + BlockVector3 get(int index); + + void add(BlockVector3 vector); + + int size(); + + void clear(); + + Iterator iterator(); + + Iterator reverseIterator(); + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/SubBlockMap.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/SubBlockMap.java new file mode 100644 index 000000000..440f52eee --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/SubBlockMap.java @@ -0,0 +1,194 @@ +/* + * 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.util.collection; + +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import it.unimi.dsi.fastutil.ints.AbstractInt2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntMaps; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.AbstractObjectSet; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectSet; + +import java.util.NoSuchElementException; +import java.util.function.BiFunction; + +/** + * Int-to-BaseBlock map, but with optimizations for common cases. + */ +class SubBlockMap extends AbstractInt2ObjectMap { + + private static boolean hasInt(BlockState b) { + return BlockStateIdAccess.getBlockStateId(b).isPresent(); + } + + private static boolean isUncommon(BaseBlock block) { + return block.hasNbtData() || !hasInt(block.toImmutableState()); + } + + private static int assumeAsInt(BlockState b) { + return BlockStateIdAccess.getBlockStateId(b) + .orElseThrow(() -> new IllegalStateException("Block state " + b + " did not have an ID")); + } + + private static BaseBlock assumeAsBlock(int id) { + if (id == Integer.MIN_VALUE) { + return null; + } + BlockState state = BlockStateIdAccess.getBlockStateById(id); + if (state == null) { + throw new IllegalStateException("No state for ID " + id); + } + return state.toBaseBlock(); + } + + static final SubBlockMap EMPTY = new SubBlockMap(); + + private final Int2IntMap commonMap = new Int2IntOpenHashMap(64, 1f); + private final Int2ObjectMap uncommonMap = new Int2ObjectOpenHashMap<>(1, 1f); + + { + commonMap.defaultReturnValue(Integer.MIN_VALUE); + } + + @Override + public int size() { + return commonMap.size() + uncommonMap.size(); + } + + @Override + public ObjectSet> int2ObjectEntrySet() { + return new AbstractObjectSet>() { + @Override + public ObjectIterator> iterator() { + return new ObjectIterator>() { + + private final ObjectIterator commonIter + = Int2IntMaps.fastIterator(commonMap); + private final ObjectIterator> uncommonIter + = Int2ObjectMaps.fastIterator(uncommonMap); + + @Override + public boolean hasNext() { + return commonIter.hasNext() || uncommonIter.hasNext(); + } + + @Override + public Entry next() { + if (commonIter.hasNext()) { + Int2IntMap.Entry e = commonIter.next(); + return new BasicEntry<>( + e.getIntKey(), assumeAsBlock(e.getIntValue()) + ); + } + if (uncommonIter.hasNext()) { + return uncommonIter.next(); + } + throw new NoSuchElementException(); + } + }; + } + + @Override + public int size() { + return SubBlockMap.this.size(); + } + }; + } + + @Override + public BaseBlock get(int key) { + int oldId = commonMap.get(key); + if (oldId == Integer.MIN_VALUE) { + return uncommonMap.get(key); + } + return assumeAsBlock(oldId); + } + + @Override + public boolean containsKey(int k) { + return commonMap.containsKey(k) || uncommonMap.containsKey(k); + } + + @Override + public boolean containsValue(Object v) { + BaseBlock block = (BaseBlock) v; + if (isUncommon(block)) { + return uncommonMap.containsValue(block); + } + return commonMap.containsValue(assumeAsInt(block.toImmutableState())); + } + + @Override + public BaseBlock put(int key, BaseBlock value) { + if (isUncommon(value)) { + BaseBlock old = uncommonMap.put(key, value); + if (old == null) { + // ensure common doesn't have the entry too + int oldId = commonMap.remove(key); + return assumeAsBlock(oldId); + } + return old; + } + int oldId = commonMap.put(key, assumeAsInt(value.toImmutableState())); + return assumeAsBlock(oldId); + } + + @Override + public BaseBlock remove(int key) { + int removed = commonMap.remove(key); + if (removed == Integer.MIN_VALUE) { + return uncommonMap.remove(key); + } + return assumeAsBlock(removed); + } + + @Override + public void replaceAll(BiFunction function) { + for (ObjectIterator iter = Int2IntMaps.fastIterator(commonMap); + iter.hasNext(); ) { + Int2IntMap.Entry next = iter.next(); + BaseBlock value = function.apply(next.getIntKey(), assumeAsBlock(next.getIntValue())); + if (isUncommon(value)) { + uncommonMap.put(next.getIntKey(), value); + iter.remove(); + } else { + next.setValue(assumeAsInt(value.toImmutableState())); + } + } + for (ObjectIterator> iter = Int2ObjectMaps.fastIterator(uncommonMap); + iter.hasNext(); ) { + Int2ObjectMap.Entry next = iter.next(); + BaseBlock value = function.apply(next.getIntKey(), next.getValue()); + if (isUncommon(value)) { + next.setValue(value); + } else { + commonMap.put(next.getIntKey(), assumeAsInt(value.toImmutableState())); + iter.remove(); + } + } + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/VectorPositionList.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/VectorPositionList.java new file mode 100644 index 000000000..6c8d27ddf --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/VectorPositionList.java @@ -0,0 +1,98 @@ +/* + * 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.util.collection; + +import com.google.common.collect.AbstractIterator; +import com.sk89q.worldedit.math.BlockVector3; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.ints.IntListIterator; + +import java.util.Iterator; + +class VectorPositionList implements PositionList { + + private final IntList delegate = new IntArrayList(); + + @Override + public BlockVector3 get(int index) { + int ri = index * 3; + return BlockVector3.at( + delegate.getInt(ri), + delegate.getInt(ri + 1), + delegate.getInt(ri + 2)); + } + + @Override + public void add(BlockVector3 vector) { + delegate.add(vector.getX()); + delegate.add(vector.getY()); + delegate.add(vector.getZ()); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public void clear() { + delegate.clear(); + } + + @Override + public Iterator iterator() { + return new AbstractIterator() { + + private final IntIterator iterator = delegate.iterator(); + + @Override + protected BlockVector3 computeNext() { + if (!iterator.hasNext()) { + return endOfData(); + } + return BlockVector3.at( + iterator.nextInt(), + iterator.nextInt(), + iterator.nextInt()); + } + }; + } + + @Override + public Iterator reverseIterator() { + return new AbstractIterator() { + + private final IntListIterator iterator = delegate.listIterator(delegate.size()); + + @Override + protected BlockVector3 computeNext() { + if (!iterator.hasPrevious()) { + return endOfData(); + } + return BlockVector3.at( + iterator.previousInt(), + iterator.previousInt(), + iterator.previousInt()); + } + }; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/concurrency/LazyReference.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/concurrency/LazyReference.java new file mode 100644 index 000000000..720ca0161 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/concurrency/LazyReference.java @@ -0,0 +1,75 @@ +/* + * 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.util.concurrency; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; + +/** + * Thread-safe lazy reference. + */ +public class LazyReference { + + public static LazyReference from(Supplier valueComputation) { + return new LazyReference<>(valueComputation); + } + + // Memory saving technique: hold the computation info in the same reference field that we'll + // put the value into, so the memory possibly retained by those parts is GC'able as soon as + // it's no longer needed. + + private static final class RefInfo { + private final Lock lock = new ReentrantLock(); + private final Supplier valueComputation; + + private RefInfo(Supplier valueComputation) { + this.valueComputation = valueComputation; + } + } + + private Object value; + + private LazyReference(Supplier valueComputation) { + this.value = new RefInfo<>(valueComputation); + } + + // casts are safe, value is either RefInfo or T + @SuppressWarnings("unchecked") + public T getValue() { + Object v = value; + if (!(v instanceof RefInfo)) { + return (T) v; + } + RefInfo refInfo = (RefInfo) v; + refInfo.lock.lock(); + try { + v = value; + if (!(v instanceof RefInfo)) { + return (T) v; + } + value = v = refInfo.valueComputation.get(); + return (T) v; + } finally { + refInfo.lock.unlock(); + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/LegacyMapper.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/LegacyMapper.java index cd9d1db06..0c4feae79 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/LegacyMapper.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/registry/LegacyMapper.java @@ -104,7 +104,7 @@ public final class LegacyMapper { if (type.hasProperty(PropertyKey.WATERLOGGED)) { blockState = blockState.with(PropertyKey.WATERLOGGED, false); } - int combinedId = getCombinedId(blockEntry.getKey()); + Integer combinedId = getCombinedId(blockEntry.getKey()); blockArr[combinedId] = blockState.getInternalId(); blockStateToLegacyId4Data.put(blockState.getInternalId(), (Integer) combinedId); @@ -221,7 +221,7 @@ public final class LegacyMapper { } public void register(int id, int data, BlockStateHolder state) { - int combinedId = ((id << 4) + data); + Integer combinedId = ((id << 4) + data); extraId4DataToStateId.put(combinedId, (Integer) state.getInternalId()); blockStateToLegacyId4Data.putIfAbsent(state.getInternalId(), combinedId); } diff --git a/worldedit-core/src/main/java/net/jpountz/util/UnsafeUtils.java b/worldedit-core/src/main/java/net/jpountz/util/UnsafeUtils.java index 665616011..b66bfce6b 100644 --- a/worldedit-core/src/main/java/net/jpountz/util/UnsafeUtils.java +++ b/worldedit-core/src/main/java/net/jpountz/util/UnsafeUtils.java @@ -43,11 +43,7 @@ public enum UnsafeUtils { INT_ARRAY_SCALE = UNSAFE.arrayIndexScale(int[].class); SHORT_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(short[].class); SHORT_ARRAY_SCALE = UNSAFE.arrayIndexScale(short[].class); - } catch (IllegalAccessException e) { - throw new ExceptionInInitializerError("Cannot access Unsafe"); - } catch (NoSuchFieldException e) { - throw new ExceptionInInitializerError("Cannot access Unsafe"); - } catch (SecurityException e) { + } catch (IllegalAccessException | NoSuchFieldException | SecurityException e) { throw new ExceptionInInitializerError("Cannot access Unsafe"); } } diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java index 15d4f8225..de9cce3cc 100644 --- a/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/internal/expression/ExpressionTest.java @@ -19,16 +19,6 @@ package com.sk89q.worldedit.internal.expression; -import static java.lang.Math.atan2; -import static java.lang.Math.sin; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; - import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.platform.Platform; @@ -36,17 +26,27 @@ import com.sk89q.worldedit.internal.expression.lexer.LexerException; import com.sk89q.worldedit.internal.expression.parser.ParserException; import com.sk89q.worldedit.internal.expression.runtime.EvaluationException; import com.sk89q.worldedit.internal.expression.runtime.ExpressionEnvironment; +import com.sk89q.worldedit.internal.expression.runtime.ExpressionTimeoutException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static java.lang.Math.atan2; import static java.lang.Math.sin; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class ExpressionTest { - @Before + + private Platform mockPlat = mock(Platform.class); + + @BeforeEach public void setup() { - Platform mockPlat = Mockito.mock(Platform.class); - Mockito.when(mockPlat.getConfiguration()).thenReturn(new LocalConfiguration() { + when(mockPlat.getConfiguration()).thenReturn(new LocalConfiguration() { @Override public void load() { } @@ -54,9 +54,14 @@ public class ExpressionTest { WorldEdit.getInstance().getPlatformManager().register(mockPlat); } + @AfterEach + public void tearDown() { + WorldEdit.getInstance().getPlatformManager().unregister(mockPlat); + } + @Test public void testEvaluate() throws ExpressionException { - // check + // check assertEquals(1 - 2 + 3, simpleEval("1 - 2 + 3"), 0); // check unary ops @@ -73,56 +78,46 @@ public class ExpressionTest { } @Test - public void testErrors() throws ExpressionException { - // test lexer errors - try { - compile("#"); - fail("Error expected"); - } catch (LexerException e) { - assertEquals("Error position", 0, e.getPosition()); - } - - // test parser errors - try { - compile("x"); - fail("Error expected"); - } catch (ParserException e) { - assertEquals("Error position", 0, e.getPosition()); - } - try { - compile("x()"); - fail("Error expected"); - } catch (ParserException e) { - assertEquals("Error position", 0, e.getPosition()); - } - try { - compile("("); - fail("Error expected"); - } catch (ParserException ignored) {} - try { - compile("x("); - fail("Error expected"); - } catch (ParserException ignored) {} - - // test overloader errors - try { - compile("atan2(1)"); - fail("Error expected"); - } catch (ParserException e) { - assertEquals("Error position", 0, e.getPosition()); - } - try { - compile("atan2(1, 2, 3)"); - fail("Error expected"); - } catch (ParserException e) { - assertEquals("Error position", 0, e.getPosition()); - } - try { - compile("rotate(1, 2, 3)"); - fail("Error expected"); - } catch (ParserException e) { - assertEquals("Error position", 0, e.getPosition()); - } + public void testErrors() { + assertAll( + // test lexer errors + () -> { + LexerException e = assertThrows(LexerException.class, + () -> compile("#")); + assertEquals(0, e.getPosition(), "Error position"); + }, + // test parser errors + () -> { + ParserException e = assertThrows(ParserException.class, + () -> compile("x")); + assertEquals(0, e.getPosition(), "Error position"); + }, + () -> { + ParserException e = assertThrows(ParserException.class, + () -> compile("x()")); + assertEquals(0, e.getPosition(), "Error position"); + }, + () -> assertThrows(ParserException.class, + () -> compile("(")), + () -> assertThrows(ParserException.class, + () -> compile("x(")), + // test overloader errors + () -> { + ParserException e = assertThrows(ParserException.class, + () -> compile("atan2(1)")); + assertEquals(0, e.getPosition(), "Error position"); + }, + () -> { + ParserException e = assertThrows(ParserException.class, + () -> compile("atan2(1, 2, 3)")); + assertEquals(0, e.getPosition(), "Error position"); + }, + () -> { + ParserException e = assertThrows(ParserException.class, + () -> compile("rotate(1, 2, 3)")); + assertEquals(0, e.getPosition(), "Error position"); + } + ); } @Test @@ -186,13 +181,11 @@ public class ExpressionTest { } @Test - public void testTimeout() throws Exception { - try { - simpleEval("for(i=0;i<256;i++){for(j=0;j<256;j++){for(k=0;k<256;k++){for(l=0;l<256;l++){ln(pi)}}}}"); - fail("Loop was not stopped."); - } catch (EvaluationException e) { - assertTrue(e.getMessage().contains("Calculations exceeded time limit")); - } + public void testTimeout() { + ExpressionTimeoutException e = assertThrows(ExpressionTimeoutException.class, + () -> simpleEval("for(i=0;i<256;i++){for(j=0;j<256;j++){for(k=0;k<256;k++){for(l=0;l<256;l++){ln(pi)}}}}"), + "Loop was not stopped."); + assertTrue(e.getMessage().contains("Calculations exceeded time limit")); } private double simpleEval(String expressionString) throws ExpressionException {