diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java index b99e2b0a..66e08568 100644 --- a/core/src/main/java/com/boydti/fawe/Fawe.java +++ b/core/src/main/java/com/boydti/fawe/Fawe.java @@ -35,7 +35,9 @@ import com.sk89q.worldedit.command.ScriptingCommands; import com.sk89q.worldedit.command.ToolCommands; import com.sk89q.worldedit.command.ToolUtilCommands; import com.sk89q.worldedit.command.composition.SelectionCommand; +import com.sk89q.worldedit.command.tool.AreaPickaxe; import com.sk89q.worldedit.command.tool.LongRangeBuildTool; +import com.sk89q.worldedit.command.tool.RecursivePickaxe; import com.sk89q.worldedit.command.tool.brush.GravityBrush; import com.sk89q.worldedit.event.extent.EditSessionEvent; import com.sk89q.worldedit.extension.factory.DefaultMaskParser; @@ -351,6 +353,8 @@ public class Fawe { // Brushes GravityBrush.inject(); // Fix for instant placement assumption LongRangeBuildTool.inject(); + AreaPickaxe.inject(); // Fixes + RecursivePickaxe.inject(); // Fixes // Selectors CuboidRegionSelector.inject(); // Translations // Visitors diff --git a/core/src/main/java/com/boydti/fawe/object/brush/RecurseBrush.java b/core/src/main/java/com/boydti/fawe/object/brush/RecurseBrush.java index 3df2784d..1443d7e2 100644 --- a/core/src/main/java/com/boydti/fawe/object/brush/RecurseBrush.java +++ b/core/src/main/java/com/boydti/fawe/object/brush/RecurseBrush.java @@ -34,22 +34,7 @@ public class RecurseBrush implements Brush { } final BlockReplace replace = new BlockReplace(editSession, to); editSession.setMask((Mask) null); - RecursiveVisitor visitor = new RecursiveVisitor(mask, replace) { - @Override - public boolean isVisitable(Vector from, Vector to) { - if (super.isVisitable(from, to)) { - int dx = Math.abs((int) (position.x - to.x)); - if (dx > radius) return false; - int dz = Math.abs((int) (position.z - to.z)); - if (dz > radius) return false; - int dy = Math.abs((int) (position.y - to.y)); - if (dy > radius) return false; - return true; - } else { - return false; - } - } - }; + RecursiveVisitor visitor = new RecursiveVisitor(mask, replace, radius); visitor.visit(position); Operations.completeBlindly(visitor); } diff --git a/core/src/main/java/com/boydti/fawe/object/mask/AdjacentMask.java b/core/src/main/java/com/boydti/fawe/object/mask/AdjacentMask.java new file mode 100644 index 00000000..18f951be --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/mask/AdjacentMask.java @@ -0,0 +1,36 @@ +package com.boydti.fawe.object.mask; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockMask; +import java.util.Collection; + +public class AdjacentMask extends BlockMask { + public AdjacentMask(Extent extent, Collection blocks) { + super(extent, blocks); + } + + @Override + public boolean test(Vector v) { + double x = v.x; + double y = v.x; + double z = v.x; + v.x = x + 1; + if (super.test(v)) { v.x = x; return true; } + v.x = x - 1; + if (super.test(v)) { v.x = x; return true; } + v.x = x; + v.y = y + 1; + if (super.test(v)) { v.y = y; return true; } + v.y = y - 1; + if (super.test(v)) { v.y = y; return true; } + v.y = y; + v.z = z + 1; + if (super.test(v)) { v.z = z; return true; } + v.z = z - 1; + if (super.test(v)) { v.z = z; return true; } + v.z = z; + return false; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/mask/CustomMask.java b/core/src/main/java/com/boydti/fawe/object/mask/CustomMask.java index ad2b2580..d48f86a2 100644 --- a/core/src/main/java/com/boydti/fawe/object/mask/CustomMask.java +++ b/core/src/main/java/com/boydti/fawe/object/mask/CustomMask.java @@ -5,6 +5,13 @@ import com.sk89q.worldedit.function.mask.Mask; import java.util.List; public abstract class CustomMask implements Mask { + + /** + * Constructor for custom mask + * @param masks Any previous masks set (usually from //mask [previous] [thismask] + * @param component The input to parse + * @param context The context (for extent, player etc) + */ public CustomMask(List masks, String component, ParserContext context) { try { this.getClass(). getConstructor ( List.class, String.class, ParserContext.class ) ; diff --git a/core/src/main/java/com/boydti/fawe/object/mask/RadiusMask.java b/core/src/main/java/com/boydti/fawe/object/mask/RadiusMask.java new file mode 100644 index 00000000..029ba0f0 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/mask/RadiusMask.java @@ -0,0 +1,52 @@ +package com.boydti.fawe.object.mask; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.Mask2D; +import javax.annotation.Nullable; + +public class RadiusMask implements Mask, ResettableMask{ + + private final int minSqr, maxSqr; + + public RadiusMask(int min, int max) { + this.minSqr = min * min; + this.maxSqr = max * max; + } + + @Override + public void reset() { + pos = null; + } + + private Vector pos; + + @Override + public boolean test(Vector to) { + if (pos == null) { + pos = new Vector(to); + } + int dx = Math.abs((int) (pos.x - to.x)); + int dy = Math.abs((int) (pos.x - to.x)); + int dz = Math.abs((int) (pos.x - to.x)); + int d = dx * dx; + if (d < minSqr || d > maxSqr) { + return false; + } + d += dz * dz; + if (d < minSqr || d > maxSqr) { + return false; + } + d += dy * dy; + if (d < minSqr || d > maxSqr) { + return false; + } + return true; + } + + @Nullable + @Override + public Mask2D toMask2D() { + return null; + } +} diff --git a/core/src/main/java/com/sk89q/worldedit/command/tool/AreaPickaxe.java b/core/src/main/java/com/sk89q/worldedit/command/tool/AreaPickaxe.java new file mode 100644 index 00000000..f0b14e28 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/command/tool/AreaPickaxe.java @@ -0,0 +1,65 @@ +package com.sk89q.worldedit.command.tool; + +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BlockID; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.world.World; + +/** + * A super pickaxe mode that will remove blocks in an area. + */ +public class AreaPickaxe implements BlockTool { + + private static final BaseBlock air = new BaseBlock(0); + private int range; + + public AreaPickaxe(int range) { + this.range = range; + } + + @Override + public boolean canUse(Actor player) { + return player.hasPermission("worldedit.superpickaxe.area"); + } + + @Override + public boolean actPrimary(Platform server, LocalConfiguration config, Player player, LocalSession session, com.sk89q.worldedit.util.Location clicked) { + int ox = clicked.getBlockX(); + int oy = clicked.getBlockY(); + int oz = clicked.getBlockZ(); + int initialType = ((World) clicked.getExtent()).getBlockType(clicked.toVector()); + + if (initialType == 0) { + return true; + } + + if (initialType == BlockID.BEDROCK && !player.canDestroyBedrock()) { + return true; + } + + EditSession editSession = session.createEditSession(player); + editSession.getSurvivalExtent().setToolUse(config.superPickaxeManyDrop); + + for (int x = ox - range; x <= ox + range; ++x) { + for (int y = oy - range; y <= oy + range; ++y) { + for (int z = oz - range; z <= oz + range; ++z) { + if (editSession.getLazyBlock(x, y, z).getId() != initialType) { + continue; + } + editSession.setBlock(x, y, z, air); + } + } + } + editSession.flushQueue(); + session.remember(editSession); + + return true; + } + + public static Class inject() { + return AreaPickaxe.class; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/worldedit/command/tool/RecursivePickaxe.java b/core/src/main/java/com/sk89q/worldedit/command/tool/RecursivePickaxe.java new file mode 100644 index 00000000..6157384f --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/command/tool/RecursivePickaxe.java @@ -0,0 +1,71 @@ +package com.sk89q.worldedit.command.tool; + +import com.boydti.fawe.object.mask.IdMask; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BlockID; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.function.block.BlockReplace; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.pattern.BlockPattern; +import com.sk89q.worldedit.function.visitor.RecursiveVisitor; +import com.sk89q.worldedit.world.World; + +/** + * A pickaxe mode that recursively finds adjacent blocks within range of + * an initial block and of the same type. + */ +public class RecursivePickaxe implements BlockTool { + + private static final BaseBlock air = new BaseBlock(0); + private double range; + + public RecursivePickaxe(double range) { + this.range = range; + } + + @Override + public boolean canUse(Actor player) { + return player.hasPermission("worldedit.superpickaxe.recursive"); + } + + @Override + public boolean actPrimary(Platform server, LocalConfiguration config, Player player, LocalSession session, com.sk89q.worldedit.util.Location clicked) { + World world = (World) clicked.getExtent(); + final Vector pos = clicked.toVector(); + + EditSession editSession = session.createEditSession(player); + + BaseBlock block = editSession.getBlock(pos); + int initialType = block.getType(); + + if (initialType == BlockID.AIR || (initialType == BlockID.BEDROCK && !player.canDestroyBedrock())) { + editSession.flushQueue(); + return true; + } + + editSession.getSurvivalExtent().setToolUse(config.superPickaxeManyDrop); + + final int radius = (int) range; + final BlockReplace replace = new BlockReplace(editSession, new BlockPattern(editSession.nullBlock)); + editSession.setMask((Mask) null); + RecursiveVisitor visitor = new RecursiveVisitor(new IdMask(editSession), replace, radius); + visitor.visit(pos); + Operations.completeBlindly(visitor); + + editSession.flushQueue(); + session.remember(editSession); + + return true; + } + + public static Class inject() { + return RecursivePickaxe.class; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java b/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java index d56ce180..a9f4fe1e 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java @@ -1,10 +1,12 @@ package com.sk89q.worldedit.extension.factory; +import com.boydti.fawe.object.mask.AdjacentMask; import com.boydti.fawe.object.mask.AngleMask; import com.boydti.fawe.object.mask.CustomMask; import com.boydti.fawe.object.mask.DataMask; import com.boydti.fawe.object.mask.IdDataMask; import com.boydti.fawe.object.mask.IdMask; +import com.boydti.fawe.object.mask.RadiusMask; import com.boydti.fawe.object.mask.XAxisMask; import com.boydti.fawe.object.mask.YAxisMask; import com.boydti.fawe.object.mask.ZAxisMask; @@ -54,7 +56,7 @@ public class DefaultMaskParser extends InputParser { super(worldEdit); } - private static CustomMask[] customMasks; + private static CustomMask[] customMasks = new CustomMask[0]; public void addMask(CustomMask mask) { checkNotNull(mask); @@ -135,16 +137,34 @@ public class DefaultMaskParser extends InputParser { case '/': { String[] split = component.substring(1).split(","); if (split.length != 2) { - throw new InputParseException("Unknown angle '" + component + "' (not in form /#,#)"); + throw new InputParseException("Unknown angle '" + component + "' (not in form `/#,#`)"); } try { int y1 = Integer.parseInt(split[0]); int y2 = Integer.parseInt(split[1]); return new AngleMask(extent, y1, y2); } catch (NumberFormatException e) { - throw new InputParseException("Unknown angle '" + component + "' (not in form /#,#)"); + throw new InputParseException("Unknown angle '" + component + "' (not in form `/#,#`)"); } } + case '{': + String[] split = component.substring(1).split(","); + if (split.length != 2) { + throw new InputParseException("Unknown range '" + component + "' (not in form `{#,#`)"); + } + try { + int y1 = Integer.parseInt(split[0]); + int y2 = Integer.parseInt(split[1]); + return new RadiusMask(y1, y2); + } catch (NumberFormatException e) { + throw new InputParseException("Unknown range '" + component + "' (not in form `{#,#`)"); + } + case '~': { + ParserContext tempContext = new ParserContext(context); + tempContext.setRestricted(false); + tempContext.setPreferringWildcard(true); + return new AdjacentMask(extent, worldEdit.getBlockFactory().parseFromListInput(component.substring(1), tempContext)); + } case '>': case '<': Mask submask; diff --git a/core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java b/core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java index 4a32dfa5..2863fda5 100644 --- a/core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java +++ b/core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java @@ -21,9 +21,14 @@ public abstract class BreadthFirstSearch implements Operation { private final List directions = new ArrayList<>(); private final Map visited; private final ArrayDeque queue; + private final int maxDepth; private int affected = 0; public BreadthFirstSearch(final RegionFunction function) { + this(function, Integer.MAX_VALUE); + } + + public BreadthFirstSearch(final RegionFunction function, int maxDepth) { this.queue = new ArrayDeque<>(); this.visited = new LinkedHashMap<>(); this.function = function; @@ -33,6 +38,7 @@ public abstract class BreadthFirstSearch implements Operation { this.directions.add(new Vector(1, 0, 0)); this.directions.add(new Vector(0, 0, -1)); this.directions.add(new Vector(0, 0, 1)); + this.maxDepth = maxDepth; } public abstract boolean isVisitable(Vector from, Vector to); @@ -53,6 +59,7 @@ public abstract class BreadthFirstSearch implements Operation { public void visit(final Vector pos) { Node node = new Node((int) pos.x, (int) pos.y, (int) pos.z); if (!this.visited.containsKey(node)) { + isVisitable(pos, pos); // Ignore this, just to initialize mask on this point visited.put(node, 0); queue.add(node); } @@ -67,8 +74,19 @@ public abstract class BreadthFirstSearch implements Operation { Vector mutable2 = new Vector(); boolean shouldTrim = false; IntegerTrio[] dirs = getIntDirections(); - for (int layer = 0; !queue.isEmpty(); layer++) { + for (int layer = 0; !queue.isEmpty() && layer <= maxDepth; layer++) { int size = queue.size(); + if (layer == maxDepth) { + visited.clear(); + for (Node current : queue) { + mutable.x = current.getX(); + mutable.y = current.getY(); + mutable.z = current.getZ(); + function.apply(mutable); + affected++; + } + break; + } for (int i = 0; i < size; i++) { from = queue.poll(); mutable.x = from.getX(); diff --git a/core/src/main/java/com/sk89q/worldedit/function/visitor/RecursiveVisitor.java b/core/src/main/java/com/sk89q/worldedit/function/visitor/RecursiveVisitor.java index ea2dae0d..dff099f7 100644 --- a/core/src/main/java/com/sk89q/worldedit/function/visitor/RecursiveVisitor.java +++ b/core/src/main/java/com/sk89q/worldedit/function/visitor/RecursiveVisitor.java @@ -22,7 +22,6 @@ package com.sk89q.worldedit.function.visitor; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.mask.Mask; -import com.sk89q.worldedit.function.operation.Operations; import static com.google.common.base.Preconditions.checkNotNull; @@ -35,14 +34,18 @@ public class RecursiveVisitor extends BreadthFirstSearch { private final Mask mask; + public RecursiveVisitor(final Mask mask, final RegionFunction function) { + this(mask, function, Integer.MAX_VALUE); + } + /** * Create a new recursive visitor. * * @param mask the mask * @param function the function */ - public RecursiveVisitor(final Mask mask, final RegionFunction function) { - super(function); + public RecursiveVisitor(final Mask mask, final RegionFunction function, int maxDepth) { + super(function, maxDepth); checkNotNull(mask); this.mask = mask; }