diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java index 1b69eea628..148b209370 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java @@ -25,7 +25,7 @@ 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.SingleBlockPatternParser; -import com.sk89q.worldedit.extension.factory.parser.pattern.TypeOrStateApplyingPatternParser; +import com.sk89q.worldedit.extension.factory.parser.pattern.PartiallyApplyingPatternParser; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.internal.registry.AbstractFactory; @@ -51,7 +51,7 @@ public PatternFactory(WorldEdit worldEdit) { // individual patterns register(new ClipboardPatternParser(worldEdit)); - register(new TypeOrStateApplyingPatternParser(worldEdit)); + register(new PartiallyApplyingPatternParser(worldEdit)); register(new RandomStatePatternParser(worldEdit)); register(new BlockCategoryPatternParser(worldEdit)); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/PartiallyApplyingPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/PartiallyApplyingPatternParser.java new file mode 100644 index 0000000000..81b7314aa1 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/PartiallyApplyingPatternParser.java @@ -0,0 +1,258 @@ +/* + * 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 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.extension.factory.parser.pattern; + +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.NoMatchException; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.buffer.ExtentBuffer; +import com.sk89q.worldedit.function.pattern.*; +import com.sk89q.worldedit.internal.registry.InputParser; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.world.block.BlockState; +import org.enginehub.linbus.format.snbt.LinStringIO; +import org.enginehub.linbus.stream.exception.NbtParseException; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + + +public class PartiallyApplyingPatternParser extends InputParser { + + boolean compatibilityMode = false; + + public PartiallyApplyingPatternParser(WorldEdit worldEdit) { + super(worldEdit); + } + + protected PartiallyApplyingPatternParser(WorldEdit worldEdit, boolean compatibilityMode) { + super(worldEdit); + this.compatibilityMode = compatibilityMode; + } + + @Override + public Stream getSuggestions(String input, ParserContext context) { + if (input.isEmpty()) { + return Stream.of("^"); + } + if (!input.startsWith("^")) { + return Stream.empty(); + } + input = input.substring(1); + + if (input.isEmpty()) { + //define properties, nbt or a type + return Stream.concat( + Stream.of("^[", "^{", "^{,"), + worldEdit.getBlockFactory().getSuggestions(input, context) + .stream() + .map(s -> "^" + s) + ); + } + + PartiallyApplyingComponents components = split(input); + + if (!components.nbt().isEmpty()) { + if (!components.type().isEmpty() && !components.properties().isEmpty()) { + //all of them are defined, we suggest like we would without ^ + return worldEdit.getBlockFactory().getSuggestions(input, context) + .stream() + .map(s -> "^" + s); + } + if (!components.type().isEmpty()) { + //type and nbt. We currently don't support nbt hints, so nothing to suggest + return Stream.empty(); + } + if (!components.properties().isEmpty()) { + //properties and nbt. We can't figure out possible nbt without type + return Stream.empty(); + } + } + + if (!components.properties().isEmpty()) { + if (!components.type().isEmpty()) { + //type and properties are defined, we suggest like we would without ^ + return worldEdit.getBlockFactory().getSuggestions(input, context) + .stream() + .map(s -> "^" + s); + } + return Stream.empty(); // without knowing a type, we can't really suggest states + } + //only type is defined, we suggest like we would without ^ + return worldEdit.getBlockFactory().getSuggestions(input, context) + .stream() + .map(s -> "^" + s); + } + + private @NotNull PartiallyApplyingComponents split(String input) { + String type; + String properties = ""; + //default as delete NBT retains previous behaviour + String nbt = compatibilityMode ? "{=}" : ""; + + int startProperties = input.indexOf('['); + int startNbt = input.indexOf('{'); + + if (startProperties >= 0 && startNbt >= 0) { + //properties and nbt and maybe type + type = input.substring(0, startProperties); + properties = input.substring(startProperties, startNbt); + nbt = input.substring(startNbt); + } else if (startProperties >= 0) { + //properties and maybe type + type = input.substring(0, startProperties); + properties = input.substring(startProperties); + } else if (startNbt >= 0) { + //nbt and maybe type + type = input.substring(0, startNbt); + nbt = input.substring(startNbt); + } else { + type = input; + } + return new PartiallyApplyingComponents(type, properties, nbt); + } + + private record PartiallyApplyingComponents(String type, String properties, String nbt) { + } + + @Override + public Pattern parseFromInput(String input, ParserContext context) throws InputParseException { + if (!input.startsWith("^")) { + return null; + } + Extent extent = context.requireExtent(); + input = input.substring(1); + + if (input.isEmpty()) { + throw new NoMatchException(TranslatableComponent.of("worldedit.error.unknown-block", TextComponent.of(input))); + } + + PartiallyApplyingComponents components = split(input); + + List extendPatternFactories = new ArrayList<>(); + + if (!components.nbt().isEmpty()) { + extendPatternFactories + .add(getNbtApplyingPatternFactory(input, components.nbt())); + } + if (!components.type().isEmpty()) { + extendPatternFactories + .add(getTypeApplyingPatternFactory(context, components.type())); + } + if (!components.properties().isEmpty()) { + extendPatternFactories + .add(getStateApplyingPatternFactory(components)); + } + + if (extendPatternFactories.size() > 1) { + Extent buffer = new ExtentBuffer(extent); + Pattern[] patterns = extendPatternFactories.stream() + .map(factory -> factory.forExtend(buffer)) + .toArray(Pattern[]::new); + return new ExtentBufferedCompositePattern(buffer, patterns); + } + + return extendPatternFactories.getFirst().forExtend(extent); + + } + + private @NotNull ExtendPatternFactory getTypeApplyingPatternFactory(ParserContext context, String type) throws InputParseException { + BlockState blockState = worldEdit.getBlockFactory() + .parseFromInput(type, context).getBlockType().getDefaultState(); + return ext -> new TypeApplyingPattern(ext, blockState); + } + + private static @NotNull ExtendPatternFactory getStateApplyingPatternFactory(PartiallyApplyingComponents components) throws InputParseException { + String properties = components.properties(); + if (!properties.endsWith("]")) { + throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-rbracket")); + } + String propertiesWithoutBrackets = properties.substring(1, properties.length() - 1); + final String[] states = propertiesWithoutBrackets.split(","); + Map statesToSet = new HashMap<>(); + for (String state : states) { + if (state.isEmpty()) { + throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-state")); + } + String[] propVal = state.split("=", 2); + if (propVal.length != 2) { + throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-equals-separator")); + } + final String prop = propVal[0]; + if (prop.isEmpty()) { + throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-property")); + } + final String value = propVal[1]; + if (value.isEmpty()) { + throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-value")); + } + if (statesToSet.put(prop, value) != null) { + throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.duplicate-property", TextComponent.of(prop))); + } + } + return ext -> new StateApplyingPattern(ext, statesToSet); + } + + private static @NotNull ExtendPatternFactory getNbtApplyingPatternFactory(String input, String nbt) throws InputParseException { + if (!nbt.endsWith("}")) { + throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-rbrace")); + } + if (nbt.equals("{}")) { + return (ext) -> new NBTApplyingPattern(ext, null); + } + boolean merge = true; + if (nbt.startsWith("{=")) { + merge = false; + nbt = "{" + nbt.substring(2); + } + LinCompoundTag tag; + try { + if (nbt.equals("{}")) { + tag = LinCompoundTag.builder().build(); + } else { + tag = LinStringIO.readFromStringUsing(nbt, LinCompoundTag::readFrom); + } + } catch (NbtParseException e) { + throw new NoMatchException(TranslatableComponent.of( + "worldedit.error.parser.invalid-nbt", + TextComponent.of("^" + input), + TextComponent.of(e.getMessage()) + )); + } + if (merge) { + return (ext) -> new NBTMergingPattern(ext, tag.value()); + } else { + return (ext) -> new NBTApplyingPattern(ext, tag); + } + } + + private interface ExtendPatternFactory { + Pattern forExtend(Extent e); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/TypeOrStateApplyingPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/TypeOrStateApplyingPatternParser.java index f343115bf7..681e969fe7 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/TypeOrStateApplyingPatternParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/TypeOrStateApplyingPatternParser.java @@ -20,109 +20,10 @@ package com.sk89q.worldedit.extension.factory.parser.pattern; import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.command.util.SuggestionHelper; -import com.sk89q.worldedit.extension.input.InputParseException; -import com.sk89q.worldedit.extension.input.ParserContext; -import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.extent.buffer.ExtentBuffer; -import com.sk89q.worldedit.function.pattern.ExtentBufferedCompositePattern; -import com.sk89q.worldedit.function.pattern.Pattern; -import com.sk89q.worldedit.function.pattern.StateApplyingPattern; -import com.sk89q.worldedit.function.pattern.TypeApplyingPattern; -import com.sk89q.worldedit.internal.registry.InputParser; -import com.sk89q.worldedit.util.formatting.text.TextComponent; -import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; -import com.sk89q.worldedit.world.block.BlockType; -import com.sk89q.worldedit.world.block.BlockTypes; - -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.stream.Stream; - - -public class TypeOrStateApplyingPatternParser extends InputParser { +@Deprecated +public class TypeOrStateApplyingPatternParser extends PartiallyApplyingPatternParser{ public TypeOrStateApplyingPatternParser(WorldEdit worldEdit) { - super(worldEdit); - } - - @Override - public Stream getSuggestions(String input, ParserContext context) { - if (input.isEmpty()) { - return Stream.of("^"); - } - if (!input.startsWith("^")) { - return Stream.empty(); - } - input = input.substring(1); - - String[] parts = input.split("\\[", 2); - String type = parts[0]; - - if (parts.length == 1) { - return worldEdit.getBlockFactory().getSuggestions(input, context).stream().map(s -> "^" + s); - } else { - if (type.isEmpty()) { - return Stream.empty(); // without knowing a type, we can't really suggest states - } else { - BlockType blockType = BlockTypes.get(type.toLowerCase(Locale.ROOT)); - return SuggestionHelper.getBlockPropertySuggestions(type, blockType, parts[1]).map(s -> "^" + s); - } - } - } - - @Override - public Pattern parseFromInput(String input, ParserContext context) throws InputParseException { - if (!input.startsWith("^")) { - return null; - } - Extent extent = context.requireExtent(); - input = input.substring(1); - - String[] parts = input.split("\\[", 2); - String type = parts[0]; - - if (parts.length == 1) { - return new TypeApplyingPattern(extent, - worldEdit.getBlockFactory().parseFromInput(type, context).getBlockType().getDefaultState()); - } else { - // states given - if (!parts[1].endsWith("]")) { - throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-rbracket")); - } - final String[] states = parts[1].substring(0, parts[1].length() - 1).split(","); - Map statesToSet = new HashMap<>(); - for (String state : states) { - if (state.isEmpty()) { - throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-state")); - } - String[] propVal = state.split("=", 2); - if (propVal.length != 2) { - throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-equals-separator")); - } - final String prop = propVal[0]; - if (prop.isEmpty()) { - throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-property")); - } - final String value = propVal[1]; - if (value.isEmpty()) { - throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-value")); - } - if (statesToSet.put(prop, value) != null) { - throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.duplicate-property", TextComponent.of(prop))); - } - } - if (type.isEmpty()) { - return new StateApplyingPattern(extent, statesToSet); - } else { - Extent buffer = new ExtentBuffer(extent); - Pattern typeApplier = new TypeApplyingPattern(buffer, - worldEdit.getBlockFactory().parseFromInput(type, context).getBlockType().getDefaultState()); - Pattern stateApplier = new StateApplyingPattern(buffer, statesToSet); - return new ExtentBufferedCompositePattern(buffer, typeApplier, stateApplier); - } - } + super(worldEdit, true); } - } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/NBTApplyingPattern.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/NBTApplyingPattern.java new file mode 100644 index 0000000000..1f4ef8a842 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/NBTApplyingPattern.java @@ -0,0 +1,22 @@ +package com.sk89q.worldedit.function.pattern; + +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import org.enginehub.linbus.tree.LinCompoundTag; + +public class NBTApplyingPattern extends AbstractExtentPattern { + private final LinCompoundTag nbtToSet; + + public NBTApplyingPattern(Extent extent, LinCompoundTag nbtToSet) { + super(extent); + this.nbtToSet = nbtToSet; + } + + @Override + public BaseBlock applyBlock(BlockVector3 position) { + BlockState block = getExtent().getBlock(position); + return block.toBaseBlock(nbtToSet); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/NBTMergingPattern.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/NBTMergingPattern.java new file mode 100644 index 0000000000..3ab986ae8a --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/NBTMergingPattern.java @@ -0,0 +1,31 @@ +package com.sk89q.worldedit.function.pattern; + +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BaseBlock; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.enginehub.linbus.tree.LinTag; + +import java.util.Map; + +public class NBTMergingPattern extends AbstractExtentPattern { + private final Map> nbtToMerge; + + public NBTMergingPattern(Extent extent, Map> nbtToMerge) { + super(extent); + this.nbtToMerge = nbtToMerge; + } + + @Override + public BaseBlock applyBlock(BlockVector3 position) { + BaseBlock baseBlock = getExtent().getFullBlock(position); + LinCompoundTag.Builder nbtBuilder; + if (baseBlock.getNbt() != null) { + nbtBuilder = baseBlock.getNbt().toBuilder(); + } else { + nbtBuilder = LinCompoundTag.builder(); + } + nbtBuilder.putAll(nbtToMerge); + return baseBlock.toBaseBlock(nbtBuilder.build()); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/StateApplyingPattern.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/StateApplyingPattern.java index 2635d2f0ee..8035ec81cc 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/StateApplyingPattern.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/StateApplyingPattern.java @@ -24,7 +24,6 @@ import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.world.block.BaseBlock; -import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; import java.util.Map; @@ -44,11 +43,11 @@ public StateApplyingPattern(Extent extent, Map statesToSet) { @Override public BaseBlock applyBlock(BlockVector3 position) { - BlockState block = getExtent().getBlock(position); + BaseBlock block = getExtent().getFullBlock(position); for (Entry, Object> entry : cache .computeIfAbsent(block.getBlockType(), (b -> resolveProperties(states, b))).entrySet()) { block = block.with(entry.getKey(), entry.getValue()); } - return block.toBaseBlock(); + return block; } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/TypeApplyingPattern.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/TypeApplyingPattern.java index 31a6276ee5..b064bebe32 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/TypeApplyingPattern.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/TypeApplyingPattern.java @@ -40,13 +40,13 @@ public TypeApplyingPattern(Extent extent, BlockState blockState) { @Override public BaseBlock applyBlock(BlockVector3 position) { - BlockState oldBlock = getExtent().getBlock(position); + BaseBlock oldBlock = getExtent().getFullBlock(position); BlockState newBlock = blockState; for (Entry, Object> entry : oldBlock.getStates().entrySet()) { @SuppressWarnings("unchecked") Property prop = (Property) entry.getKey(); newBlock = newBlock.with(prop, entry.getValue()); } - return newBlock.toBaseBlock(); + return newBlock.toBaseBlock(oldBlock.getNbtReference()); } }