Some new brushes

Scatter - Set a pattern at random points on a surface
ScatterCommand - Runs a command at random points on a surface
Splatter - Recursively set blocks at random points on a surface
This commit is contained in:
Jesse Boyd 2017-03-11 20:50:37 +11:00
parent fb76ff4ea0
commit d765b24b27
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
15 changed files with 362 additions and 43 deletions

View File

@ -5,7 +5,6 @@ import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.input.InputParseException;
import com.sk89q.worldedit.extension.input.ParserContext;
import com.sk89q.worldedit.internal.registry.InputParser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -14,29 +13,6 @@ public abstract class FaweParser<T> extends InputParser<T> {
super(worldEdit);
}
public List<String> split(String input, char delim) {
List<String> result = new ArrayList<String>();
int start = 0;
int bracket = 0;
boolean inQuotes = false;
for (int current = 0; current < input.length(); current++) {
char currentChar = input.charAt(current);
boolean atLastChar = (current == input.length() - 1);
if (!atLastChar && (bracket > 0 || (currentChar == '{' && ++bracket > 0) || (current == '}' && --bracket <= 0))) continue;
if (currentChar == '\"') inQuotes = !inQuotes; // toggle state
if(atLastChar) result.add(input.substring(start));
else if (currentChar == delim && !inQuotes) {
String toAdd = input.substring(start, current);
if (toAdd.startsWith("\"")) {
toAdd = toAdd.substring(1, toAdd.length() - 1);
}
result.add(toAdd);
start = current + 1;
}
}
return result;
}
public T catchSuggestion(String currentInput, String nextInput, ParserContext context) throws InputParseException {
try {
return parseFromInput(nextInput, context);
@ -47,7 +23,7 @@ public abstract class FaweParser<T> extends InputParser<T> {
}
public List<String> suggestRemaining(String input, String... expected) throws InputParseException {
List<String> remainder = split(input, ':');
List<String> remainder = StringMan.split(input, ':');
int len = remainder.size();
if (len != expected.length - 1) {
if (len <= expected.length - 1 && len != 0) {

View File

@ -121,6 +121,7 @@ public enum BBC {
BRUSH_HEIGHT_INVALID("Invalid height map file (%s0)", "WorldEdit.Brush"),
BRUSH_SMOOTH("Smooth brush equipped (%s0 x %s1 using %s2. Note: Use the blend brush if you want to smooth overhangs or caves.).", "WorldEdit.Brush"),
BRUSH_SPHERE("Sphere brush shape equipped (%s0).", "WorldEdit.Brush"),
BRUSH_SCATTER("Scatter brush shape equipped (%s0, %s1).", "WorldEdit.Brush"),
BRUSH_SHATTER("Shatter brush shape equipped (%s0, %s1).", "WorldEdit.Brush"),
BRUSH_STENCIL("Stencil brush equipped (%s0).", "WorldEdit.Brush"),
BRUSH_LINE("Line brush shape equipped (%s0).", "WorldEdit.Brush"),

View File

@ -1,6 +1,7 @@
package com.boydti.fawe.object.brush;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.util.StringMan;
import com.boydti.fawe.wrappers.LocationMaskedPlayerWrapper;
import com.boydti.fawe.wrappers.PlayerWrapper;
import com.boydti.fawe.wrappers.SilentPlayerWrapper;
@ -15,25 +16,25 @@ import com.sk89q.worldedit.extension.platform.CommandManager;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.util.Location;
import java.util.List;
public class CommandBrush implements Brush {
private final String command;
private final int radius;
public CommandBrush(String command, double radius) {
this.command = command;
this.radius = (int) radius;
}
@Override
public void build(EditSession editSession, Vector position, Pattern pattern, double size) throws MaxChangedBlocksException {
int radius = (int) size;
CuboidRegionSelector selector = new CuboidRegionSelector(editSession.getWorld(), position.subtract(radius, radius, radius), position.add(radius, radius, radius));
String replaced = command.replace("{x}", position.getBlockX() + "")
.replace("{y}", position.getBlockY() + "")
.replace("{z}", position.getBlockZ() + "")
.replace("{y}", Integer.toString(position.getBlockY()))
.replace("{z}", Integer.toString(position.getBlockZ()))
.replace("{world}", editSession.getQueue().getWorldName())
.replace("{size}", radius + "");
.replace("{size}", Integer.toString(radius));
FawePlayer fp = editSession.getPlayer();
Player player = fp.getPlayer();
@ -45,7 +46,7 @@ public class CommandBrush implements Brush {
}
fp.setSelection(selector);
PlayerWrapper wePlayer = new SilentPlayerWrapper(new LocationMaskedPlayerWrapper(player, new Location(player.getExtent(), position)));
String[] cmds = replaced.split(";");
List<String> cmds = StringMan.split(replaced, ';');
for (String cmd : cmds) {
CommandEvent event = new CommandEvent(wePlayer, cmd);
CommandManager.getInstance().handleCommandOnCurrentThread(event);

View File

@ -0,0 +1,91 @@
package com.boydti.fawe.object.brush;
import com.boydti.fawe.object.PseudoRandom;
import com.boydti.fawe.object.collection.BlockVectorSet;
import com.boydti.fawe.object.collection.LocalBlockVectorSet;
import com.boydti.fawe.object.mask.AdjacentAnyMask;
import com.boydti.fawe.object.mask.RadiusMask;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.command.tool.brush.Brush;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.Masks;
import com.sk89q.worldedit.function.mask.SolidBlockMask;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.visitor.BreadthFirstSearch;
import com.sk89q.worldedit.function.visitor.RecursiveVisitor;
import java.util.Arrays;
public class ScatterBrush implements Brush {
private final int count;
private final int distance;
private Mask mask;
public ScatterBrush(int count, int distance) {
this.count = count;
this.distance = distance;
}
public int getDistance() {
return distance;
}
public int getCount() {
return count;
}
@Override
public void build(EditSession editSession, Vector position, Pattern pattern, double size) throws MaxChangedBlocksException {
// pick a bunch of random points
// expand randomly from them
this.mask = editSession.getMask();
if (this.mask == null) {
this.mask = Masks.alwaysTrue();
}
final AdjacentAnyMask adjacent = new AdjacentAnyMask(editSession, Arrays.asList(new BaseBlock(0)));
final SolidBlockMask solid = new SolidBlockMask(editSession);
final RadiusMask radius = new RadiusMask(0, (int) size);
final int distance = Math.min((int) size, this.distance);
RecursiveVisitor visitor = new RecursiveVisitor(vector -> solid.test(vector) && radius.test(vector) && adjacent.test(vector), function -> true);
visitor.visit(position);
visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS));
Operations.completeBlindly(visitor);
BlockVectorSet visited = visitor.getVisited();
int length = visited.size();
LocalBlockVectorSet placed = new LocalBlockVectorSet();
int maxFails = 1000;
for (int i = 0; i < count; i++) {
int index = PseudoRandom.random.nextInt(length);
Vector pos = visited.get(index);
if (pos != null && canApply(editSession, pos)) {
int x = pos.getBlockX();
int y = pos.getBlockY();
int z = pos.getBlockZ();
if (placed.containsRadius(x, y, z, distance)) {
if (maxFails-- <= 0) {
break;
}
i--;
continue;
}
placed.add(x, y, z);
apply(editSession, placed, pos, pattern, size);
}
}
}
public boolean canApply(EditSession editSession, Vector pos) {
return mask.test(pos);
}
public void apply(EditSession editSession, LocalBlockVectorSet placed, Vector pt, Pattern p, double size) throws MaxChangedBlocksException {
editSession.setBlock(pt, p);
}
}

View File

@ -0,0 +1,48 @@
package com.boydti.fawe.object.brush;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.collection.LocalBlockVectorSet;
import com.boydti.fawe.util.StringMan;
import com.boydti.fawe.wrappers.LocationMaskedPlayerWrapper;
import com.boydti.fawe.wrappers.PlayerWrapper;
import com.boydti.fawe.wrappers.SilentPlayerWrapper;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.platform.CommandEvent;
import com.sk89q.worldedit.extension.platform.CommandManager;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.util.Location;
import java.util.List;
public class ScatterCommand extends ScatterBrush{
private final String command;
public ScatterCommand(int count, int distance, String command) {
super(count, distance);
this.command = command;
}
@Override
public void apply(EditSession editSession, LocalBlockVectorSet placed, Vector position, Pattern p, double size) throws MaxChangedBlocksException {
int radius = getDistance();
CuboidRegionSelector selector = new CuboidRegionSelector(editSession.getWorld(), position.subtract(radius, radius, radius), position.add(radius, radius, radius));
String replaced = command.replace("{x}", position.getBlockX() + "")
.replace("{y}", Integer.toString(position.getBlockY()))
.replace("{z}", Integer.toString(position.getBlockZ()))
.replace("{world}", editSession.getQueue().getWorldName())
.replace("{size}", Integer.toString(radius));
FawePlayer fp = editSession.getPlayer();
Player player = fp.getPlayer();
fp.setSelection(selector);
PlayerWrapper wePlayer = new SilentPlayerWrapper(new LocationMaskedPlayerWrapper(player, new Location(player.getExtent(), position)));
List<String> cmds = StringMan.split(replaced, ';');
for (String cmd : cmds) {
CommandEvent event = new CommandEvent(wePlayer, cmd);
CommandManager.getInstance().handleCommandOnCurrentThread(event);
}
}
}

View File

@ -0,0 +1,63 @@
package com.boydti.fawe.object.brush;
import com.boydti.fawe.object.PseudoRandom;
import com.boydti.fawe.object.collection.LocalBlockVectorSet;
import com.boydti.fawe.object.mask.AdjacentAnyMask;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.SolidBlockMask;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.BlockPattern;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.visitor.RecursiveVisitor;
import java.util.Arrays;
public class SplatterBrush extends ScatterBrush {
private final boolean solid;
private final int recursion;
public SplatterBrush(int count, int distance, boolean solid) {
super(count, 1);
this.recursion = distance;
this.solid = solid;
}
@Override
public void apply(final EditSession editSession, final LocalBlockVectorSet placed, final Vector position, Pattern p, double size) throws MaxChangedBlocksException {
final Pattern finalPattern;
if (solid) {
finalPattern = new BlockPattern(p.apply(position));
} else {
finalPattern = p;
}
final int size2 = (int) (size * size);
final AdjacentAnyMask adjacent = new AdjacentAnyMask(editSession, Arrays.asList(new BaseBlock(0)));
final SolidBlockMask solid = new SolidBlockMask(editSession);
RecursiveVisitor visitor = new RecursiveVisitor(new Mask() {
@Override
public boolean test(Vector vector) {
double dist = vector.distanceSq(position);
if (!placed.contains(vector) && (PseudoRandom.random.random(5) < 2) && solid.test(vector) && adjacent.test(vector)) {
placed.add(vector);
return true;
}
return false;
}
}, new RegionFunction() {
@Override
public boolean apply(Vector vector) throws WorldEditException {
return editSession.setBlock(vector, finalPattern);
}
}, recursion, editSession);
visitor.setMaxBranch(2);
visitor.setDirections(Arrays.asList(visitor.DIAGONAL_DIRECTIONS));
visitor.visit(position);
Operations.completeBlindly(visitor);
}
}

View File

@ -3,7 +3,6 @@ package com.boydti.fawe.object.brush;
import com.boydti.fawe.object.PseudoRandom;
import com.boydti.fawe.object.brush.heightmap.HeightMap;
import com.boydti.fawe.object.mask.AdjacentAnyMask;
import com.boydti.fawe.object.mask.RadiusMask;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
@ -45,11 +44,10 @@ public class StencilBrush extends HeightBrush {
final AdjacentAnyMask adjacent = new AdjacentAnyMask(editSession, Arrays.asList(new BaseBlock(0)));
final SolidBlockMask solid = new SolidBlockMask(editSession);
RegionMask region = new RegionMask(new CuboidRegion(position.subtract(size, size, size), position.add(size, size, size)));
final RadiusMask radius = new RadiusMask(0, size);
RecursiveVisitor visitor = new RecursiveVisitor(new Mask() {
@Override
public boolean test(Vector vector) {
if (solid.test(vector) && radius.test(vector)) {
if (solid.test(vector) && region.test(vector)) {
int dx = vector.getBlockX() - cx;
int dy = vector.getBlockY() - cy;
int dz = vector.getBlockZ() - cz;

View File

@ -32,6 +32,31 @@ public class BlockVectorSet extends AbstractCollection<Vector> implements Set<Ve
return size;
}
public Vector get(int index) {
int count = 0;
ObjectIterator<Int2ObjectMap.Entry<LocalBlockVectorSet>> iter = localSets.int2ObjectEntrySet().iterator();
while (iter.hasNext()) {
Int2ObjectMap.Entry<LocalBlockVectorSet> entry = iter.next();
LocalBlockVectorSet set = entry.getValue();
int size = set.size();
int newSize = count + size;
if (newSize > index) {
int localIndex = index - count;
Vector pos = set.getIndex(localIndex);
if (pos != null) {
int pair = entry.getIntKey();
int cx = MathMan.unpairX(pair);
int cz = MathMan.unpairY(pair);
pos.mutX((cx << 11) + pos.getBlockX());
pos.mutZ((cz << 11) + pos.getBlockZ());
return pos;
}
}
count = newSize;
}
return null;
}
@Override
public boolean isEmpty() {
for (Int2ObjectMap.Entry<LocalBlockVectorSet> entry : localSets.int2ObjectEntrySet()) {

View File

@ -61,6 +61,9 @@ public class LocalBlockVectorSet implements Set<Vector> {
}
public boolean containsRadius(int x, int y, int z, int radius) {
if (radius <= 0) {
return contains(x, y, z);
}
int length = radius * 2;
if (size() < length * length * length) {
int index = -1;
@ -97,6 +100,28 @@ public class LocalBlockVectorSet implements Set<Vector> {
this.offsetZ = z;
}
public Vector getIndex(int getIndex) {
int size = size();
if (getIndex > size) {
return null;
}
int index = -1;
for (int i = 0; i < getIndex; i++) {
index = set.nextSetBit(index + 1);
}
if (index != -1) {
int b1 = (index & 0xFF);
int b2 = ((byte) (index >> 8)) & 0x7F;
int b3 = ((byte)(index >> 15)) & 0xFF;
int b4 = ((byte) (index >> 23)) & 0xFF;
int x = offsetX + (((b3 + ((MathMan.unpair8x(b2)) << 8)) << 21) >> 21);
int y = b1;
int z = offsetZ + (((b4 + ((MathMan.unpair8y(b2)) << 8)) << 21) >> 21);
return MutableBlockVector.get(x, y, z);
}
return null;
}
@Override
public Iterator<Vector> iterator() {
return new Iterator<Vector>() {

View File

@ -1,9 +1,11 @@
package com.boydti.fawe.util;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@ -30,6 +32,29 @@ public class StringMan {
return sb.toString();
}
public static List<String> split(String input, char delim) {
List<String> result = new ArrayList<String>();
int start = 0;
int bracket = 0;
boolean inQuotes = false;
for (int current = 0; current < input.length(); current++) {
char currentChar = input.charAt(current);
boolean atLastChar = (current == input.length() - 1);
if (!atLastChar && (bracket > 0 || (currentChar == '{' && ++bracket > 0) || (current == '}' && --bracket <= 0))) continue;
if (currentChar == '\"') inQuotes = !inQuotes; // toggle state
if(atLastChar) result.add(input.substring(start));
else if (currentChar == delim && !inQuotes) {
String toAdd = input.substring(start, current);
if (toAdd.startsWith("\"")) {
toAdd = toAdd.substring(1, toAdd.length() - 1);
}
result.add(toAdd);
start = current + 1;
}
}
return result;
}
public static int intersection(final Set<String> options, final String[] toCheck) {
int count = 0;
for (final String check : toCheck) {

View File

@ -34,7 +34,10 @@ import com.boydti.fawe.object.brush.HeightBrush;
import com.boydti.fawe.object.brush.LineBrush;
import com.boydti.fawe.object.brush.RaiseBrush;
import com.boydti.fawe.object.brush.RecurseBrush;
import com.boydti.fawe.object.brush.ScatterBrush;
import com.boydti.fawe.object.brush.ScatterCommand;
import com.boydti.fawe.object.brush.ShatterBrush;
import com.boydti.fawe.object.brush.SplatterBrush;
import com.boydti.fawe.object.brush.SplineBrush;
import com.boydti.fawe.object.brush.StencilBrush;
import com.boydti.fawe.object.brush.TargetMode;
@ -537,7 +540,8 @@ public class BrushCommands {
desc = "Use a height map to paint a surface",
help =
"Chooses the stencil brush.\n" +
"The -w flag will only apply at maximum saturation",
"The -w flag will only apply at maximum saturation\n" +
"The -r flag will apply random rotation",
min = 1,
max = -1
)
@ -561,6 +565,62 @@ public class BrushCommands {
player.print(BBC.getPrefix() + BBC.BRUSH_STENCIL.f(radius));
}
@Command(
aliases = { "scatter" },
usage = "<pattern> [radius=5] [points=5] [distance=1]",
desc = "Scatter blocks on a surface",
help =
"Chooses the scatter brush.",
min = 1,
max = 4
)
@CommandPermissions("worldedit.brush.scatter")
public void scatterBrush(Player player, EditSession editSession, LocalSession session, Pattern fill, @Optional("5") double radius, @Optional("5") double points, @Optional("1") double distance) throws WorldEditException {
worldEdit.checkMaxBrushRadius(radius);
BrushTool tool = session.getBrushTool(player);
tool.setFill(fill);
tool.setSize(radius);
tool.setBrush(new ScatterBrush((int) points, (int) distance), "worldedit.brush.scatter", player);
player.print(BBC.getPrefix() + BBC.BRUSH_SCATTER.f(radius, points));
}
@Command(
aliases = { "splatter" },
usage = "<pattern> [radius=5] [seeds=1] [recursion=5] [solid=true]",
desc = "Splatter blocks on a surface",
help =
"Chooses the Splatter brush.",
min = 1,
max = 4
)
@CommandPermissions("worldedit.brush.splatter")
public void splatterBrush(Player player, EditSession editSession, LocalSession session, Pattern fill, @Optional("5") double radius, @Optional("1") double points, @Optional("5") double recursion, @Optional("true") boolean solid) throws WorldEditException {
worldEdit.checkMaxBrushRadius(radius);
BrushTool tool = session.getBrushTool(player);
tool.setFill(fill);
tool.setSize(radius);
tool.setBrush(new SplatterBrush((int) points, (int) recursion, solid), "worldedit.brush.splatter", player);
player.print(BBC.getPrefix() + BBC.BRUSH_SCATTER.f(radius, points));
}
@Command(
aliases = { "scmd", "scattercmd", "scattercommand", "scommand" },
usage = "<scatter-radius> <points> <cmd-radius=1> <cmd1;cmd2...>",
desc = "Scatter commands on a surface",
help =
"Chooses the scatter command brush.",
min = 1,
max = -1
)
@CommandPermissions("worldedit.brush.scattercommand")
public void scatterCommandBrush(Player player, EditSession editSession, LocalSession session, double radius, double points, double distance, CommandContext args) throws WorldEditException {
worldEdit.checkMaxBrushRadius(radius);
BrushTool tool = session.getBrushTool(player);
tool.setSize(radius);
tool.setBrush(new ScatterCommand((int) points, (int) distance, args.getJoinedStrings(3)), "worldedit.brush.scattercommand", player);
player.print(BBC.getPrefix() + BBC.BRUSH_SCATTER.f(radius, points));
}
@Command(
aliases = { "cylinder", "cyl", "c" },
usage = "<block> [radius] [height]",
@ -822,7 +882,7 @@ public class BrushCommands {
max = 99
)
@CommandPermissions("worldedit.brush.command")
public void command(Player player, LocalSession session, @Optional("5") double radius, CommandContext args) throws WorldEditException {
public void command(Player player, LocalSession session, double radius, CommandContext args) throws WorldEditException {
BrushTool tool = session.getBrushTool(player);
String cmd = args.getJoinedStrings(1);
tool.setBrush(new CommandBrush(cmd, radius), "worldedit.brush.copy", player);

View File

@ -90,13 +90,13 @@ public class DefaultMaskParser extends FaweParser<Mask> {
public Mask parseFromInput(String input, ParserContext context) throws InputParseException {
List<Mask> masks = new ArrayList<Mask>();
for (String component : split(input, ' ')) {
for (String component : StringMan.split(input, ' ')) {
if (component.isEmpty()) {
continue;
}
HashSet<BaseBlock> blocks = new HashSet<BaseBlock>();
List<Mask> masksUnion = new ArrayList<Mask>();
for (String elem : split(component, ',')) {
for (String elem : StringMan.split(component, ',')) {
ArrayList<Mask> list = new ArrayList<Mask>();
list.add(catchSuggestion(input, list, elem, context));
if (list.size() == 1) {

View File

@ -197,7 +197,7 @@ public class HashTagPatternParser extends FaweParser<Pattern> {
case "#l":
case "#linear": {
ArrayList<Pattern> patterns = new ArrayList<>();
for (String token : split(rest, ',')) {
for (String token : StringMan.split(rest, ',')) {
patterns.add(catchSuggestion(input, token, context));
}
if (patterns.isEmpty()) {
@ -208,7 +208,7 @@ public class HashTagPatternParser extends FaweParser<Pattern> {
case "#l3d":
case "#linear3d": {
ArrayList<Pattern> patterns = new ArrayList<>();
for (String token : split(rest, ',')) {
for (String token : StringMan.split(rest, ',')) {
patterns.add(catchSuggestion(input, token, context));
}
if (patterns.isEmpty()) {
@ -250,7 +250,7 @@ public class HashTagPatternParser extends FaweParser<Pattern> {
case "<block>":
throw new SuggestInputParseException(input, BundledBlockData.getInstance().getBlockNames());
}
List<String> items = split(input, ',');
List<String> items = StringMan.split(input, ',');
if (items.size() == 1) {
return new BlockPattern(worldEdit.getBlockFactory().parseFromInput(items.get(0), context));
}

View File

@ -2,7 +2,6 @@ package com.sk89q.worldedit.function.pattern;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.function.pattern.AbstractPattern;
import static com.google.common.base.Preconditions.checkNotNull;

View File

@ -60,6 +60,7 @@ public abstract class BreadthFirstSearch implements Operation {
private BlockVectorSet queue;
private final int maxDepth;
private int affected = 0;
private int maxBranch = Integer.MAX_VALUE;
public BreadthFirstSearch(final RegionFunction function) {
this(function, Integer.MAX_VALUE);
@ -118,6 +119,10 @@ public abstract class BreadthFirstSearch implements Operation {
return visited.contains(pos);
}
public void setMaxBranch(int maxBranch) {
this.maxBranch = maxBranch;
}
@Override
public Operation resume(RunContext run) throws WorldEditException {
MutableBlockVector mutable = new MutableBlockVector();
@ -151,7 +156,8 @@ public abstract class BreadthFirstSearch implements Operation {
}
for (Vector from : queue) {
if (function.apply(from)) affected++;
for (IntegerTrio direction : dirs) {
for (int i = 0, j = 0; i < dirs.length && j < maxBranch; i++) {
IntegerTrio direction = dirs[i];
int y = from.getBlockY() + direction.y;
if (y < 0 || y >= 256) {
continue;
@ -163,6 +169,7 @@ public abstract class BreadthFirstSearch implements Operation {
mutable2.mutY(y);
mutable2.mutZ(z);
if (isVisitable(from, mutable2)) {
j++;
visited.add(x, y, z);
tempQueue.add(x, y, z);
}