Skip to content

Commit 4f58081

Browse files
committed
make ^ support nbt (properly)
1 parent 4726327 commit 4f58081

File tree

7 files changed

+320
-109
lines changed

7 files changed

+320
-109
lines changed

worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import com.sk89q.worldedit.extension.factory.parser.pattern.RandomPatternParser;
2626
import com.sk89q.worldedit.extension.factory.parser.pattern.RandomStatePatternParser;
2727
import com.sk89q.worldedit.extension.factory.parser.pattern.SingleBlockPatternParser;
28-
import com.sk89q.worldedit.extension.factory.parser.pattern.TypeOrStateApplyingPatternParser;
28+
import com.sk89q.worldedit.extension.factory.parser.pattern.PartiallyApplyingPatternParser;
2929
import com.sk89q.worldedit.function.pattern.Pattern;
3030
import com.sk89q.worldedit.internal.registry.AbstractFactory;
3131

@@ -51,7 +51,7 @@ public PatternFactory(WorldEdit worldEdit) {
5151

5252
// individual patterns
5353
register(new ClipboardPatternParser(worldEdit));
54-
register(new TypeOrStateApplyingPatternParser(worldEdit));
54+
register(new PartiallyApplyingPatternParser(worldEdit));
5555
register(new RandomStatePatternParser(worldEdit));
5656
register(new BlockCategoryPatternParser(worldEdit));
5757
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/*
2+
* WorldEdit, a Minecraft world manipulation toolkit
3+
* Copyright (C) sk89q <http://www.sk89q.com>
4+
* Copyright (C) WorldEdit team and contributors
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
package com.sk89q.worldedit.extension.factory.parser.pattern;
21+
22+
import com.sk89q.worldedit.WorldEdit;
23+
import com.sk89q.worldedit.extension.input.InputParseException;
24+
import com.sk89q.worldedit.extension.input.NoMatchException;
25+
import com.sk89q.worldedit.extension.input.ParserContext;
26+
import com.sk89q.worldedit.extent.Extent;
27+
import com.sk89q.worldedit.extent.buffer.ExtentBuffer;
28+
import com.sk89q.worldedit.function.pattern.*;
29+
import com.sk89q.worldedit.internal.registry.InputParser;
30+
import com.sk89q.worldedit.util.formatting.text.TextComponent;
31+
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
32+
import com.sk89q.worldedit.world.block.BlockState;
33+
import org.enginehub.linbus.format.snbt.LinStringIO;
34+
import org.enginehub.linbus.stream.exception.NbtParseException;
35+
import org.enginehub.linbus.tree.LinCompoundTag;
36+
import org.jetbrains.annotations.NotNull;
37+
38+
import java.util.ArrayList;
39+
import java.util.HashMap;
40+
import java.util.List;
41+
import java.util.Map;
42+
import java.util.stream.Stream;
43+
44+
45+
public class PartiallyApplyingPatternParser extends InputParser<Pattern> {
46+
47+
boolean compatibilityMode = false;
48+
49+
public PartiallyApplyingPatternParser(WorldEdit worldEdit) {
50+
super(worldEdit);
51+
}
52+
53+
protected PartiallyApplyingPatternParser(WorldEdit worldEdit, boolean compatibilityMode) {
54+
super(worldEdit);
55+
this.compatibilityMode = compatibilityMode;
56+
}
57+
58+
@Override
59+
public Stream<String> getSuggestions(String input, ParserContext context) {
60+
if (input.isEmpty()) {
61+
return Stream.of("^");
62+
}
63+
if (!input.startsWith("^")) {
64+
return Stream.empty();
65+
}
66+
input = input.substring(1);
67+
68+
if (input.isEmpty()) {
69+
//define properties, nbt or a type
70+
return Stream.concat(
71+
Stream.of("^[", "^{", "^{,"),
72+
worldEdit.getBlockFactory().getSuggestions(input, context)
73+
.stream()
74+
.map(s -> "^" + s)
75+
);
76+
}
77+
78+
PartiallyApplyingComponents components = split(input);
79+
80+
if (!components.nbt().isEmpty()) {
81+
if (!components.type().isEmpty() && !components.properties().isEmpty()) {
82+
//all of them are defined, we suggest like we would without ^
83+
return worldEdit.getBlockFactory().getSuggestions(input, context)
84+
.stream()
85+
.map(s -> "^" + s);
86+
}
87+
if (!components.type().isEmpty()) {
88+
//type and nbt. We currently don't support nbt hints, so nothing to suggest
89+
return Stream.empty();
90+
}
91+
if (!components.properties().isEmpty()) {
92+
//properties and nbt. We can't figure out possible nbt without type
93+
return Stream.empty();
94+
}
95+
}
96+
97+
if (!components.properties().isEmpty()) {
98+
if (!components.type().isEmpty()) {
99+
//type and properties are defined, we suggest like we would without ^
100+
return worldEdit.getBlockFactory().getSuggestions(input, context)
101+
.stream()
102+
.map(s -> "^" + s);
103+
}
104+
return Stream.empty(); // without knowing a type, we can't really suggest states
105+
}
106+
//only type is defined, we suggest like we would without ^
107+
return worldEdit.getBlockFactory().getSuggestions(input, context)
108+
.stream()
109+
.map(s -> "^" + s);
110+
}
111+
112+
private @NotNull PartiallyApplyingComponents split(String input) {
113+
String type;
114+
String properties = "";
115+
//default as delete NBT retains previous behaviour
116+
String nbt = compatibilityMode ? "{=}" : "";
117+
118+
int startProperties = input.indexOf('[');
119+
int startNbt = input.indexOf('{');
120+
121+
if (startProperties >= 0 && startNbt >= 0) {
122+
//properties and nbt and maybe type
123+
type = input.substring(0, startProperties);
124+
properties = input.substring(startProperties, startNbt);
125+
nbt = input.substring(startNbt);
126+
} else if (startProperties >= 0) {
127+
//properties and maybe type
128+
type = input.substring(0, startProperties);
129+
properties = input.substring(startProperties);
130+
} else if (startNbt >= 0) {
131+
//nbt and maybe type
132+
type = input.substring(0, startNbt);
133+
nbt = input.substring(startNbt);
134+
} else {
135+
type = input;
136+
}
137+
return new PartiallyApplyingComponents(type, properties, nbt);
138+
}
139+
140+
private record PartiallyApplyingComponents(String type, String properties, String nbt) {
141+
}
142+
143+
@Override
144+
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
145+
if (!input.startsWith("^")) {
146+
return null;
147+
}
148+
Extent extent = context.requireExtent();
149+
input = input.substring(1);
150+
151+
if (input.isEmpty()) {
152+
throw new NoMatchException(TranslatableComponent.of("worldedit.error.unknown-block", TextComponent.of(input)));
153+
}
154+
155+
PartiallyApplyingComponents components = split(input);
156+
157+
List<ExtendPatternFactory> extendPatternFactories = new ArrayList<>();
158+
159+
if (!components.nbt().isEmpty()) {
160+
extendPatternFactories
161+
.add(getNbtApplyingPatternFactory(input, components.nbt()));
162+
}
163+
if (!components.type().isEmpty()) {
164+
extendPatternFactories
165+
.add(getTypeApplyingPatternFactory(context, components.type()));
166+
}
167+
if (!components.properties().isEmpty()) {
168+
extendPatternFactories
169+
.add(getStateApplyingPatternFactory(components));
170+
}
171+
172+
if (extendPatternFactories.size() > 1) {
173+
Extent buffer = new ExtentBuffer(extent);
174+
Pattern[] patterns = extendPatternFactories.stream()
175+
.map(factory -> factory.forExtend(buffer))
176+
.toArray(Pattern[]::new);
177+
return new ExtentBufferedCompositePattern(buffer, patterns);
178+
}
179+
180+
return extendPatternFactories.getFirst().forExtend(extent);
181+
182+
}
183+
184+
private @NotNull ExtendPatternFactory getTypeApplyingPatternFactory(ParserContext context, String type) throws InputParseException {
185+
BlockState blockState = worldEdit.getBlockFactory()
186+
.parseFromInput(type, context).getBlockType().getDefaultState();
187+
return ext -> new TypeApplyingPattern(ext, blockState);
188+
}
189+
190+
private static @NotNull ExtendPatternFactory getStateApplyingPatternFactory(PartiallyApplyingComponents components) throws InputParseException {
191+
String properties = components.properties();
192+
if (!properties.endsWith("]")) {
193+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-rbracket"));
194+
}
195+
String propertiesWithoutBrackets = properties.substring(1, properties.length() - 1);
196+
final String[] states = propertiesWithoutBrackets.split(",");
197+
Map<String, String> statesToSet = new HashMap<>();
198+
for (String state : states) {
199+
if (state.isEmpty()) {
200+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-state"));
201+
}
202+
String[] propVal = state.split("=", 2);
203+
if (propVal.length != 2) {
204+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-equals-separator"));
205+
}
206+
final String prop = propVal[0];
207+
if (prop.isEmpty()) {
208+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-property"));
209+
}
210+
final String value = propVal[1];
211+
if (value.isEmpty()) {
212+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-value"));
213+
}
214+
if (statesToSet.put(prop, value) != null) {
215+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.duplicate-property", TextComponent.of(prop)));
216+
}
217+
}
218+
return ext -> new StateApplyingPattern(ext, statesToSet);
219+
}
220+
221+
private static @NotNull ExtendPatternFactory getNbtApplyingPatternFactory(String input, String nbt) throws InputParseException {
222+
if (!nbt.endsWith("}")) {
223+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-rbrace"));
224+
}
225+
if (nbt.equals("{}")) {
226+
return (ext) -> new NBTApplyingPattern(ext, null);
227+
}
228+
boolean merge = true;
229+
if (nbt.startsWith("{=")) {
230+
merge = false;
231+
nbt = "{" + nbt.substring(2);
232+
}
233+
LinCompoundTag tag;
234+
try {
235+
if (nbt.equals("{}")) {
236+
tag = LinCompoundTag.builder().build();
237+
} else {
238+
tag = LinStringIO.readFromStringUsing(nbt, LinCompoundTag::readFrom);
239+
}
240+
} catch (NbtParseException e) {
241+
throw new NoMatchException(TranslatableComponent.of(
242+
"worldedit.error.parser.invalid-nbt",
243+
TextComponent.of("^" + input),
244+
TextComponent.of(e.getMessage())
245+
));
246+
}
247+
if (merge) {
248+
return (ext) -> new NBTMergingPattern(ext, tag.value());
249+
} else {
250+
return (ext) -> new NBTApplyingPattern(ext, tag);
251+
}
252+
}
253+
254+
private interface ExtendPatternFactory {
255+
Pattern forExtend(Extent e);
256+
}
257+
258+
}

worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/TypeOrStateApplyingPatternParser.java

+3-102
Original file line numberDiff line numberDiff line change
@@ -20,109 +20,10 @@
2020
package com.sk89q.worldedit.extension.factory.parser.pattern;
2121

2222
import com.sk89q.worldedit.WorldEdit;
23-
import com.sk89q.worldedit.command.util.SuggestionHelper;
24-
import com.sk89q.worldedit.extension.input.InputParseException;
25-
import com.sk89q.worldedit.extension.input.ParserContext;
26-
import com.sk89q.worldedit.extent.Extent;
27-
import com.sk89q.worldedit.extent.buffer.ExtentBuffer;
28-
import com.sk89q.worldedit.function.pattern.ExtentBufferedCompositePattern;
29-
import com.sk89q.worldedit.function.pattern.Pattern;
30-
import com.sk89q.worldedit.function.pattern.StateApplyingPattern;
31-
import com.sk89q.worldedit.function.pattern.TypeApplyingPattern;
32-
import com.sk89q.worldedit.internal.registry.InputParser;
33-
import com.sk89q.worldedit.util.formatting.text.TextComponent;
34-
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
35-
import com.sk89q.worldedit.world.block.BlockType;
36-
import com.sk89q.worldedit.world.block.BlockTypes;
37-
38-
import java.util.HashMap;
39-
import java.util.Locale;
40-
import java.util.Map;
41-
import java.util.stream.Stream;
42-
43-
44-
public class TypeOrStateApplyingPatternParser extends InputParser<Pattern> {
4523

24+
@Deprecated
25+
public class TypeOrStateApplyingPatternParser extends PartiallyApplyingPatternParser{
4626
public TypeOrStateApplyingPatternParser(WorldEdit worldEdit) {
47-
super(worldEdit);
48-
}
49-
50-
@Override
51-
public Stream<String> getSuggestions(String input, ParserContext context) {
52-
if (input.isEmpty()) {
53-
return Stream.of("^");
54-
}
55-
if (!input.startsWith("^")) {
56-
return Stream.empty();
57-
}
58-
input = input.substring(1);
59-
60-
String[] parts = input.split("\\[", 2);
61-
String type = parts[0];
62-
63-
if (parts.length == 1) {
64-
return worldEdit.getBlockFactory().getSuggestions(input, context).stream().map(s -> "^" + s);
65-
} else {
66-
if (type.isEmpty()) {
67-
return Stream.empty(); // without knowing a type, we can't really suggest states
68-
} else {
69-
BlockType blockType = BlockTypes.get(type.toLowerCase(Locale.ROOT));
70-
return SuggestionHelper.getBlockPropertySuggestions(type, blockType, parts[1]).map(s -> "^" + s);
71-
}
72-
}
73-
}
74-
75-
@Override
76-
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
77-
if (!input.startsWith("^")) {
78-
return null;
79-
}
80-
Extent extent = context.requireExtent();
81-
input = input.substring(1);
82-
83-
String[] parts = input.split("\\[", 2);
84-
String type = parts[0];
85-
86-
if (parts.length == 1) {
87-
return new TypeApplyingPattern(extent,
88-
worldEdit.getBlockFactory().parseFromInput(type, context).getBlockType().getDefaultState());
89-
} else {
90-
// states given
91-
if (!parts[1].endsWith("]")) {
92-
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-rbracket"));
93-
}
94-
final String[] states = parts[1].substring(0, parts[1].length() - 1).split(",");
95-
Map<String, String> statesToSet = new HashMap<>();
96-
for (String state : states) {
97-
if (state.isEmpty()) {
98-
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-state"));
99-
}
100-
String[] propVal = state.split("=", 2);
101-
if (propVal.length != 2) {
102-
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-equals-separator"));
103-
}
104-
final String prop = propVal[0];
105-
if (prop.isEmpty()) {
106-
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-property"));
107-
}
108-
final String value = propVal[1];
109-
if (value.isEmpty()) {
110-
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-value"));
111-
}
112-
if (statesToSet.put(prop, value) != null) {
113-
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.duplicate-property", TextComponent.of(prop)));
114-
}
115-
}
116-
if (type.isEmpty()) {
117-
return new StateApplyingPattern(extent, statesToSet);
118-
} else {
119-
Extent buffer = new ExtentBuffer(extent);
120-
Pattern typeApplier = new TypeApplyingPattern(buffer,
121-
worldEdit.getBlockFactory().parseFromInput(type, context).getBlockType().getDefaultState());
122-
Pattern stateApplier = new StateApplyingPattern(buffer, statesToSet);
123-
return new ExtentBufferedCompositePattern(buffer, typeApplier, stateApplier);
124-
}
125-
}
27+
super(worldEdit, true);
12628
}
127-
12829
}

0 commit comments

Comments
 (0)