geforkt von Mirrors/FastAsyncWorldEdit
Start reimplementation of simplex pattern (#520)
* Start reimplementation of simplex pattern * Fix suggestions * Allow nested weighted patterns * Add documentation and improve error handling * Remove unnecessary code and obsolete TODOs
Dieser Commit ist enthalten in:
Ursprung
6be429cc96
Commit
0bb6bc3563
@ -20,6 +20,7 @@
|
|||||||
package com.sk89q.util;
|
package com.sk89q.util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -331,4 +332,37 @@ public final class StringUtil {
|
|||||||
|
|
||||||
return parsableBlocks;
|
return parsableBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits a string respecting enclosing quotes.
|
||||||
|
*
|
||||||
|
* @param input the input to split.
|
||||||
|
* @param delimiter the delimiter to split on.
|
||||||
|
* @param open the opening quote character.
|
||||||
|
* @param close the closing quote character.
|
||||||
|
* @return a list of split strings.
|
||||||
|
*/
|
||||||
|
public static List<String> split(String input, char delimiter, char open, char close) {
|
||||||
|
if (input.indexOf(open) == -1 && input.indexOf(close) == -1) {
|
||||||
|
return Arrays.asList(input.split(String.valueOf(delimiter)));
|
||||||
|
}
|
||||||
|
int level = 0;
|
||||||
|
int begin = 0;
|
||||||
|
List<String> split = new ArrayList<>();
|
||||||
|
for (int i = 0; i < input.length(); i++) {
|
||||||
|
char c = input.charAt(i);
|
||||||
|
if (c == delimiter && level == 0) {
|
||||||
|
split.add(input.substring(begin, i));
|
||||||
|
begin = i + 1;
|
||||||
|
} else if (c == open) {
|
||||||
|
level++;
|
||||||
|
} else if (c == close) {
|
||||||
|
level--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (begin < input.length()) {
|
||||||
|
split.add(input.substring(begin));
|
||||||
|
}
|
||||||
|
return split;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,8 +58,8 @@ public class BlockFactory extends AbstractFactory<BaseBlock> {
|
|||||||
*/
|
*/
|
||||||
public Set<BaseBlock> parseFromListInput(String input, ParserContext context) throws InputParseException {
|
public Set<BaseBlock> parseFromListInput(String input, ParserContext context) throws InputParseException {
|
||||||
Set<BaseBlock> blocks = new HashSet<>();
|
Set<BaseBlock> blocks = new HashSet<>();
|
||||||
String[] splits = input.split(",");
|
// String[] splits = input.split(",");
|
||||||
for (String token : StringUtil.parseListInQuotes(splits, ',', '[', ']', true)) {
|
for (String token : StringUtil.split(input, ',', '[', ']')) {
|
||||||
blocks.add(parseFromInput(token, context));
|
blocks.add(parseFromInput(token, context));
|
||||||
}
|
}
|
||||||
return blocks;
|
return blocks;
|
||||||
|
@ -24,6 +24,7 @@ import com.sk89q.worldedit.extension.factory.parser.pattern.BlockCategoryPattern
|
|||||||
import com.sk89q.worldedit.extension.factory.parser.pattern.ClipboardPatternParser;
|
import com.sk89q.worldedit.extension.factory.parser.pattern.ClipboardPatternParser;
|
||||||
import com.sk89q.worldedit.extension.factory.parser.pattern.RandomPatternParser;
|
import com.sk89q.worldedit.extension.factory.parser.pattern.RandomPatternParser;
|
||||||
import com.sk89q.worldedit.extension.factory.parser.pattern.RandomStatePatternParser;
|
import com.sk89q.worldedit.extension.factory.parser.pattern.RandomStatePatternParser;
|
||||||
|
import com.sk89q.worldedit.extension.factory.parser.pattern.SimplexPatternParser;
|
||||||
import com.sk89q.worldedit.extension.factory.parser.pattern.SingleBlockPatternParser;
|
import com.sk89q.worldedit.extension.factory.parser.pattern.SingleBlockPatternParser;
|
||||||
import com.sk89q.worldedit.extension.factory.parser.pattern.TypeOrStateApplyingPatternParser;
|
import com.sk89q.worldedit.extension.factory.parser.pattern.TypeOrStateApplyingPatternParser;
|
||||||
import com.sk89q.worldedit.function.pattern.Pattern;
|
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||||
@ -54,6 +55,9 @@ public final class PatternFactory extends AbstractFactory<Pattern> {
|
|||||||
register(new TypeOrStateApplyingPatternParser(worldEdit));
|
register(new TypeOrStateApplyingPatternParser(worldEdit));
|
||||||
register(new RandomStatePatternParser(worldEdit));
|
register(new RandomStatePatternParser(worldEdit));
|
||||||
register(new BlockCategoryPatternParser(worldEdit));
|
register(new BlockCategoryPatternParser(worldEdit));
|
||||||
|
|
||||||
|
// FAWE
|
||||||
|
register(new SimplexPatternParser(worldEdit));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,118 @@
|
|||||||
|
package com.sk89q.worldedit.extension.factory.parser;
|
||||||
|
|
||||||
|
import com.sk89q.util.StringUtil;
|
||||||
|
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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A rich parser allows parsing of patterns and masks with extra arguments,
|
||||||
|
* e.g. #simplex[scale][pattern].
|
||||||
|
*
|
||||||
|
* @param <E> the parse result.
|
||||||
|
*/
|
||||||
|
public abstract class RichParser<E> extends InputParser<E> {
|
||||||
|
private final String prefix;
|
||||||
|
private final String required;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new rich parser with a defined prefix for the result, e.g. {@code #simplex}.
|
||||||
|
*
|
||||||
|
* @param worldEdit the worldedit instance.
|
||||||
|
* @param prefix the prefix of this parser result.
|
||||||
|
*/
|
||||||
|
protected RichParser(WorldEdit worldEdit, String prefix) {
|
||||||
|
super(worldEdit);
|
||||||
|
this.prefix = prefix;
|
||||||
|
this.required = prefix + "[";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<String> getSuggestions(String input) {
|
||||||
|
// we don't even want to start suggesting if it's not meant to be this parser result
|
||||||
|
if (input.length() > this.required.length() && !input.startsWith(this.required)) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
// suggest until the first [ as long as it isn't fully typed
|
||||||
|
if (input.length() < this.required.length()) {
|
||||||
|
return Stream.of(this.required).filter(s -> s.startsWith(input));
|
||||||
|
}
|
||||||
|
// we know that it is at least "<required>"
|
||||||
|
String[] strings = extractArguments(input.substring(this.prefix.length()), false);
|
||||||
|
StringJoiner joiner = new StringJoiner(",");
|
||||||
|
for (int i = 0; i < strings.length - 1; i++) {
|
||||||
|
joiner.add("[" + strings[i] + "]");
|
||||||
|
}
|
||||||
|
String previous = this.prefix + joiner;
|
||||||
|
return getSuggestions(strings[strings.length - 1], strings.length - 1).map(s -> previous + "[" + s + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public E parseFromInput(String input, ParserContext context) throws InputParseException {
|
||||||
|
if (!input.startsWith(this.prefix)) return null;
|
||||||
|
if (input.length() < this.prefix.length()) return null;
|
||||||
|
String[] arguments = extractArguments(input.substring(prefix.length()), true);
|
||||||
|
return parseFromInput(arguments, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a stream of suggestions for the argument at the given index.
|
||||||
|
*
|
||||||
|
* @param argumentInput the already provided input for the argument at the given index.
|
||||||
|
* @param index the index of the argument to get suggestions for.
|
||||||
|
* @return a stream of suggestions matching the given input for the argument at the given index.
|
||||||
|
*/
|
||||||
|
protected abstract Stream<String> getSuggestions(String argumentInput, int index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the already split arguments.
|
||||||
|
*
|
||||||
|
* @param arguments the array of arguments that were split (can be empty).
|
||||||
|
* @param context the context of this parsing process.
|
||||||
|
* @return the resulting parsed type.
|
||||||
|
* @throws InputParseException if the input couldn't be parsed correctly.
|
||||||
|
*/
|
||||||
|
protected abstract E parseFromInput(@NotNull String[] arguments, ParserContext context) throws InputParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts arguments enclosed by {@code []} into an array.
|
||||||
|
* Example: {@code [Hello][World]} results in a list containing {@code Hello} and {@code World}.
|
||||||
|
*
|
||||||
|
* @param input the input to extract arguments from.
|
||||||
|
* @param requireClosing whether or not the extraction requires valid bracketing.
|
||||||
|
* @return an array of extracted arguments.
|
||||||
|
* @throws InputParseException if {@code requireClosing == true} and the count of [ != the count of ]
|
||||||
|
*/
|
||||||
|
protected String[] extractArguments(String input, boolean requireClosing) throws InputParseException {
|
||||||
|
int open = 0; // the "level"
|
||||||
|
int openIndex = 0;
|
||||||
|
int i = 0;
|
||||||
|
List<String> arguments = new ArrayList<>();
|
||||||
|
for (; i < input.length(); i++) {
|
||||||
|
if (input.charAt(i) == '[') {
|
||||||
|
if (open++ == 0) {
|
||||||
|
openIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (input.charAt(i) == ']') {
|
||||||
|
if (--open == 0) {
|
||||||
|
arguments.add(input.substring(openIndex + 1, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!requireClosing && open > 0) {
|
||||||
|
arguments.add(input.substring(openIndex + 1));
|
||||||
|
}
|
||||||
|
if (requireClosing && open != 0) {
|
||||||
|
throw new InputParseException("Invalid bracketing, are you missing a '[' or ']'?");
|
||||||
|
}
|
||||||
|
return arguments.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
}
|
@ -38,8 +38,9 @@ public class RandomPatternParser extends InputParser<Pattern> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<String> getSuggestions(String input) {
|
public Stream<String> getSuggestions(String input) {
|
||||||
String[] splits = input.split(",", -1);
|
List<String> patterns = StringUtil.split(input, ',', '[', ']');
|
||||||
List<String> patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']', true);
|
/*String[] splits = input.split(",", -1);
|
||||||
|
List<String> patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']', true);*/
|
||||||
if (patterns.size() == 1) {
|
if (patterns.size() == 1) {
|
||||||
return Stream.empty();
|
return Stream.empty();
|
||||||
}
|
}
|
||||||
@ -63,8 +64,9 @@ public class RandomPatternParser extends InputParser<Pattern> {
|
|||||||
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
|
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
|
||||||
RandomPattern randomPattern = new RandomPattern();
|
RandomPattern randomPattern = new RandomPattern();
|
||||||
|
|
||||||
String[] splits = input.split(",", -1);
|
List<String> patterns = StringUtil.split(input, ',', '[', ']');
|
||||||
List<String> patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']', true);
|
/*String[] splits = input.split(",", -1);
|
||||||
|
List<String> patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']', true);*/
|
||||||
if (patterns.size() == 1) {
|
if (patterns.size() == 1) {
|
||||||
return null; // let a 'single'-pattern parser handle it
|
return null; // let a 'single'-pattern parser handle it
|
||||||
}
|
}
|
||||||
@ -74,7 +76,7 @@ public class RandomPatternParser extends InputParser<Pattern> {
|
|||||||
|
|
||||||
// Parse special percentage syntax
|
// Parse special percentage syntax
|
||||||
if (token.matches("[0-9]+(\\.[0-9]*)?%.*")) {
|
if (token.matches("[0-9]+(\\.[0-9]*)?%.*")) {
|
||||||
String[] p = token.split("%");
|
String[] p = token.split("%", 2);
|
||||||
|
|
||||||
if (p.length < 2) {
|
if (p.length < 2) {
|
||||||
throw new InputParseException("Missing the type after the % symbol for '" + input + "'");
|
throw new InputParseException("Missing the type after the % symbol for '" + input + "'");
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
package com.sk89q.worldedit.extension.factory.parser.pattern;
|
||||||
|
|
||||||
|
import com.boydti.fawe.object.random.SimplexRandom;
|
||||||
|
import com.sk89q.worldedit.WorldEdit;
|
||||||
|
import com.sk89q.worldedit.extension.factory.parser.RichParser;
|
||||||
|
import com.sk89q.worldedit.extension.input.InputParseException;
|
||||||
|
import com.sk89q.worldedit.extension.input.ParserContext;
|
||||||
|
import com.sk89q.worldedit.function.pattern.Pattern;
|
||||||
|
import com.sk89q.worldedit.function.pattern.RandomPattern;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockStateHolder;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class SimplexPatternParser extends RichParser<Pattern> {
|
||||||
|
private static final String SIMPLEX_PREFIX = "#simplex";
|
||||||
|
|
||||||
|
public SimplexPatternParser(WorldEdit worldEdit) {
|
||||||
|
super(worldEdit, SIMPLEX_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Stream<String> getSuggestions(String argumentInput, int index) {
|
||||||
|
if (index == 0) {
|
||||||
|
if (argumentInput.isEmpty()) {
|
||||||
|
return Stream.of("1", "2", "3", "4", "5", "6", "7", "8", "9");
|
||||||
|
}
|
||||||
|
// if already a valid number, suggest more digits
|
||||||
|
if (isDouble(argumentInput)) {
|
||||||
|
Stream<String> numbers = Stream.of("", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
|
||||||
|
if (argumentInput.indexOf('.') == -1) {
|
||||||
|
numbers = Stream.concat(numbers, Stream.of("."));
|
||||||
|
}
|
||||||
|
return numbers.map(s -> argumentInput + s);
|
||||||
|
}
|
||||||
|
// no valid input anymore
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
if (index == 1) {
|
||||||
|
return worldEdit.getPatternFactory().getSuggestions(argumentInput).stream();
|
||||||
|
}
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Pattern parseFromInput(@NotNull String[] arguments, ParserContext context) {
|
||||||
|
if (arguments.length != 2) {
|
||||||
|
throw new InputParseException("Simplex requires a scale and a pattern, e.g. #simplex[5][dirt,stone]");
|
||||||
|
}
|
||||||
|
double scale = Double.parseDouble(arguments[0]);
|
||||||
|
scale = 1d / Math.max(1, scale);
|
||||||
|
Pattern inner = worldEdit.getPatternFactory().parseFromInput(arguments[1], context);
|
||||||
|
if (inner instanceof RandomPattern) {
|
||||||
|
return new RandomPattern(new SimplexRandom(scale), (RandomPattern) inner);
|
||||||
|
} else if (inner instanceof BlockStateHolder) {
|
||||||
|
return inner; // single blocks won't have any impact on how simplex behaves
|
||||||
|
} else {
|
||||||
|
throw new InputParseException("Pattern " + inner.getClass().getSimpleName() + " cannot be used with #simplex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDouble(String input) {
|
||||||
|
boolean point = false;
|
||||||
|
for (char c : input.toCharArray()) {
|
||||||
|
if (!Character.isDigit(c)) {
|
||||||
|
if (c == '.' && !point) {
|
||||||
|
point = true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -52,6 +52,19 @@ public class RandomPattern extends AbstractPattern {
|
|||||||
this.random = random;
|
this.random = random;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a random pattern from an existing one but with a different random.
|
||||||
|
*
|
||||||
|
* @param random the new random to use.
|
||||||
|
* @param parent the existing random pattern.
|
||||||
|
*/
|
||||||
|
public RandomPattern(SimpleRandom random, RandomPattern parent) {
|
||||||
|
this.random = random;
|
||||||
|
this.weights = parent.weights;
|
||||||
|
this.collection = RandomCollection.of(weights, random);
|
||||||
|
this.patterns = parent.patterns;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a pattern to the weight list of patterns.
|
* Add a pattern to the weight list of patterns.
|
||||||
*
|
*
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren