From 88014b18a38a93d650bb092ae62a5a4cc0fbb06b Mon Sep 17 00:00:00 2001 From: wizjany Date: Tue, 12 Feb 2019 16:39:09 -0500 Subject: [PATCH] Added a few new things using block states. * `//set ##*tag` sets all states in the tag (not just default state per type) * `//set ^type` is a pattern changing block type but copying all valid existing states * `//set ^[prop=val,...]` sets the property `prop` to `val` wherever the existing block has that property * `//set ^type[prop=val,...]` does both of the above Those work anywhere a pattern is taken, of course. * The mask syntax `^[prop=val]` matches blocks with the property `prop` set to `val`, or blocks that don't have the property at all. * The mask syntax `^=[prop=val]` only matches blocks that have the property. Those work anywhere a mask is taken, of course. (`//mask`, `//gmask`, `//replace`, etc) The `//drain` command now takes `-w` flag that removes the waterlogged state from blocks (in addition to removing water, as before). --- .../java/com/sk89q/worldedit/EditSession.java | 35 ++++++- .../com/sk89q/worldedit/blocks/Blocks.java | 28 ++++++ .../worldedit/command/UtilityCommands.java | 6 +- .../extension/factory/MaskFactory.java | 2 + .../extension/factory/PatternFactory.java | 13 ++- .../parser/mask/BlockStateMaskParser.java | 60 ++++++++++++ .../pattern/BlockCategoryPatternParser.java | 29 +++++- .../parser/pattern/RandomPatternParser.java | 21 ++-- .../TypeOrStateApplyingPatternParser.java | 79 +++++++++++++++ .../worldedit/extent/buffer/ExtentBuffer.java | 97 +++++++++++++++++++ .../function/mask/BlockStateMask.java | 70 +++++++++++++ .../pattern/AbstractExtentPattern.java | 39 ++++++++ .../ExtentBufferedCompositePattern.java | 66 +++++++++++++ .../function/pattern/ExtentPattern.java | 32 ++++++ .../pattern/RepeatingExtentPattern.java | 26 +---- .../pattern/StateApplyingPattern.java | 54 +++++++++++ .../function/pattern/TypeApplyingPattern.java | 52 ++++++++++ .../function/pattern/WaterloggedRemover.java | 47 +++++++++ .../world/block/BlockCategories.java | 4 +- 19 files changed, 711 insertions(+), 49 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/TypeOrStateApplyingPatternParser.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extent/buffer/ExtentBuffer.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockStateMask.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/AbstractExtentPattern.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/ExtentBufferedCompositePattern.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/ExtentPattern.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/StateApplyingPattern.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/TypeApplyingPattern.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/WaterloggedRemover.java diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java index f490af6c2..f44a217ac 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -53,6 +53,7 @@ import com.sk89q.worldedit.function.block.Naturalizer; import com.sk89q.worldedit.function.generator.GardenPatchGenerator; import com.sk89q.worldedit.function.mask.BlockMask; import com.sk89q.worldedit.function.mask.BlockTypeMask; +import com.sk89q.worldedit.function.mask.BlockStateMask; import com.sk89q.worldedit.function.mask.BoundedHeightMask; import com.sk89q.worldedit.function.mask.ExistingBlockMask; import com.sk89q.worldedit.function.mask.Mask; @@ -68,6 +69,7 @@ import com.sk89q.worldedit.function.operation.OperationQueue; 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.pattern.WaterloggedRemover; import com.sk89q.worldedit.function.util.RegionOffset; import com.sk89q.worldedit.function.visitor.DownwardVisitor; import com.sk89q.worldedit.function.visitor.LayerVisitor; @@ -115,7 +117,9 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.registry.LegacyMapper; +import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -125,8 +129,6 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import javax.annotation.Nullable; - /** * An {@link Extent} that handles history, {@link BlockBag}s, change limits, * block re-ordering, and much more. Most operations in WorldEdit use this class. @@ -1283,15 +1285,38 @@ public class EditSession implements Extent, AutoCloseable { * @throws MaxChangedBlocksException thrown if too many blocks are changed */ public int drainArea(BlockVector3 origin, double radius) throws MaxChangedBlocksException { + return drainArea(origin, radius, false); + } + + /** + * Drain nearby pools of water or lava, optionally removed waterlogged states from blocks. + * + * @param origin the origin to drain from, which will search a 3x3 area + * @param radius the radius of the removal, where a value should be 0 or greater + * @param waterlogged true to make waterlogged blocks non-waterlogged as well + * @return number of blocks affected + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int drainArea(BlockVector3 origin, double radius, boolean waterlogged) throws MaxChangedBlocksException { checkNotNull(origin); checkArgument(radius >= 0, "radius >= 0 required"); MaskIntersection mask = new MaskIntersection( new BoundedHeightMask(0, getWorld().getMaxY()), new RegionMask(new EllipsoidRegion(null, origin, Vector3.at(radius, radius, radius))), - getWorld().createLiquidMask()); + waterlogged ? new MaskUnion( + getWorld().createLiquidMask(), + new BlockStateMask(this, new HashMap() {{ + put("waterlogged", "true"); + }}, true)) + : getWorld().createLiquidMask()); - BlockReplace replace = new BlockReplace(this, new BlockPattern(BlockTypes.AIR.getDefaultState())); + BlockReplace replace; + if (waterlogged) { + replace = new BlockReplace(this, new WaterloggedRemover(this)); + } else { + replace = new BlockReplace(this, new BlockPattern(BlockTypes.AIR.getDefaultState())); + } RecursiveVisitor visitor = new RecursiveVisitor(mask, replace); // Around the origin in a 3x3 block @@ -2197,7 +2222,7 @@ public class EditSession implements Extent, AutoCloseable { try { if (expression.evaluate(scaled.getX(), scaled.getZ()) <= 0) { - return null; // TODO should return OUTSIDE? seems to cause issues otherwise, workedaround for now + return null; } // TODO: Allow biome setting via a script variable (needs BiomeType<->int mapping) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/Blocks.java b/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/Blocks.java index 2e8366c24..1f65c6a19 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/Blocks.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/blocks/Blocks.java @@ -19,9 +19,13 @@ package com.sk89q.worldedit.blocks; +import com.google.common.collect.Maps; +import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; import java.util.Collection; +import java.util.Map; /** * Block-related utility methods. @@ -48,4 +52,28 @@ public final class Blocks { return false; } + /** + * Parses a string->string map to find the matching Property and values for the given BlockType. + * + * @param states the desired states and values + * @param type the block type to get properties and values for + * @return a property->value map + */ + public static Map, Object> resolveProperties(Map states, BlockType type) { + Map> existing = type.getPropertyMap(); + Map, Object> newMap = Maps.newHashMap(); + states.forEach((key, value) -> { + @SuppressWarnings("unchecked") + Property prop = (Property) existing.get(key); + if (prop == null) return; + Object val = null; + try { + val = prop.getValueFor(value); + } catch (IllegalArgumentException ignored) { + } + if (val == null) return; + newMap.put(prop, val); + }); + return newMap; + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java index 86867a786..3a4425f88 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java @@ -142,7 +142,10 @@ public class UtilityCommands { @Command( aliases = { "/drain" }, usage = "", + flags = "w", desc = "Drain a pool", + help = "Removes all connected water sources.\n" + + " If -w is specified, also makes waterlogged blocks non-waterlogged.", min = 1, max = 1 ) @@ -151,9 +154,10 @@ public class UtilityCommands { public void drain(Player player, LocalSession session, EditSession editSession, CommandContext args) throws WorldEditException { double radius = Math.max(0, args.getDouble(0)); + boolean waterlogged = args.hasFlag('w'); we.checkMaxRadius(radius); int affected = editSession.drainArea( - session.getPlacementPosition(player), radius); + session.getPlacementPosition(player), radius, waterlogged); player.print(affected + " block(s) have been changed."); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/MaskFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/MaskFactory.java index b19cd6766..29bb9eb25 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/MaskFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/MaskFactory.java @@ -22,6 +22,7 @@ package com.sk89q.worldedit.extension.factory; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.factory.parser.mask.BiomeMaskParser; import com.sk89q.worldedit.extension.factory.parser.mask.BlockCategoryMaskParser; +import com.sk89q.worldedit.extension.factory.parser.mask.BlockStateMaskParser; import com.sk89q.worldedit.extension.factory.parser.mask.BlocksMaskParser; import com.sk89q.worldedit.extension.factory.parser.mask.ExistingMaskParser; import com.sk89q.worldedit.extension.factory.parser.mask.ExpressionMaskParser; @@ -67,6 +68,7 @@ public final class MaskFactory extends AbstractFactory { register(new OffsetMaskParser(worldEdit)); register(new BiomeMaskParser(worldEdit)); register(new NoiseMaskParser(worldEdit)); + register(new BlockStateMaskParser(worldEdit)); register(new NegateMaskParser(worldEdit)); register(new ExpressionMaskParser(worldEdit)); register(new BlocksMaskParser(worldEdit)); 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 838194538..8c23c4ef8 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 @@ -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.RandomPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.SingleBlockPatternParser; +import com.sk89q.worldedit.extension.factory.parser.pattern.TypeOrStateApplyingPatternParser; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.internal.registry.AbstractFactory; @@ -44,10 +45,16 @@ public final class PatternFactory extends AbstractFactory { public PatternFactory(WorldEdit worldEdit) { super(worldEdit); - register(new ClipboardPatternParser(worldEdit)); - register(new BlockCategoryPatternParser(worldEdit)); - register(new SingleBlockPatternParser(worldEdit)); + // split and parse each sub-pattern register(new RandomPatternParser(worldEdit)); + + // individual patterns + register(new BlockCategoryPatternParser(worldEdit)); + register(new ClipboardPatternParser(worldEdit)); + register(new TypeOrStateApplyingPatternParser(worldEdit)); + + // inner-most pattern: just one block + register(new SingleBlockPatternParser(worldEdit)); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java new file mode 100644 index 000000000..f76346f27 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/mask/BlockStateMaskParser.java @@ -0,0 +1,60 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.extension.factory.parser.mask; + +import com.google.common.base.Splitter; +import com.google.common.collect.Maps; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockStateMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.NoiseFilter; +import com.sk89q.worldedit.internal.registry.InputParser; +import com.sk89q.worldedit.math.noise.RandomNoise; +import com.sk89q.worldedit.session.request.Request; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class BlockStateMaskParser extends InputParser { + + public BlockStateMaskParser(WorldEdit worldEdit) { + super(worldEdit); + } + + @Override + public Mask parseFromInput(String input, ParserContext context) throws InputParseException { + if (!(input.startsWith("^[") || input.startsWith("^=[")) || !input.endsWith("]")) { + return null; + } + Extent extent = Request.request().getEditSession(); + boolean strict = input.charAt(1) == '='; + String states = input.substring(2 + (strict ? 1 : 0), input.length() - 1); + try { + return new BlockStateMask(extent, + Splitter.on(',').omitEmptyStrings().trimResults().withKeyValueSeparator('=').split(states), + strict); + } catch (Exception e) { + throw new InputParseException("Invalid states.", e); + } + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BlockCategoryPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BlockCategoryPatternParser.java index 3b89b3cef..5b08053b0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BlockCategoryPatternParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/BlockCategoryPatternParser.java @@ -28,9 +28,11 @@ import com.sk89q.worldedit.function.pattern.RandomPattern; import com.sk89q.worldedit.internal.registry.InputParser; import com.sk89q.worldedit.world.block.BlockCategories; import com.sk89q.worldedit.world.block.BlockCategory; +import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; public class BlockCategoryPatternParser extends InputParser { @@ -46,17 +48,34 @@ public class BlockCategoryPatternParser extends InputParser { @Override public Pattern parseFromInput(String input, ParserContext context) throws InputParseException { - if(!input.startsWith("##")) { + if (!input.startsWith("##")) { return null; } - BlockCategory category = BlockCategories.get(input.substring(2).toLowerCase()); + String tag = input.substring(2).toLowerCase(); + boolean anyState = false; + if (tag.startsWith("*")) { + tag = tag.substring(1); + anyState = true; + } + + BlockCategory category = BlockCategories.get(tag); if (category == null) { - throw new InputParseException("Unknown block tag: " + input.substring(2)); + throw new InputParseException("Unknown block tag: " + tag); } RandomPattern randomPattern = new RandomPattern(); - for (BlockType blockType : category.getAll()) { - randomPattern.add(new BlockPattern(blockType.getDefaultState()), 1.0 / category.getAll().size()); + Set blocks = category.getAll(); + if (blocks.isEmpty()) { + throw new InputParseException("Block tag " + category.getId() + " had no blocks!"); + } + + if (anyState) { + blocks.stream().flatMap(blockType -> blockType.getAllStates().stream()).forEach(state -> + randomPattern.add(new BlockPattern(state), 1.0)); + } else { + for (BlockType blockType : blocks) { + randomPattern.add(new BlockPattern(blockType.getDefaultState()), 1.0); + } } return randomPattern; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java index 400e6efd7..68c768954 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RandomPatternParser.java @@ -21,14 +21,13 @@ package com.sk89q.worldedit.extension.factory.parser.pattern; import com.sk89q.util.StringUtil; import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.extension.factory.BlockFactory; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; -import com.sk89q.worldedit.function.pattern.BlockPattern; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.RandomPattern; import com.sk89q.worldedit.internal.registry.InputParser; -import com.sk89q.worldedit.world.block.BaseBlock; + +import java.util.List; public class RandomPatternParser extends InputParser { @@ -38,14 +37,16 @@ public class RandomPatternParser extends InputParser { @Override public Pattern parseFromInput(String input, ParserContext context) throws InputParseException { - BlockFactory blockRegistry = worldEdit.getBlockFactory(); RandomPattern randomPattern = new RandomPattern(); String[] splits = input.split(","); - for (String token : StringUtil.parseListInQuotes(splits, ',', '[', ']')) { - BaseBlock block; - + List patterns = StringUtil.parseListInQuotes(splits, ',', '[', ']'); + if (patterns.size() == 1) { + return null; // let a 'single'-pattern parser handle it + } + for (String token : patterns) { double chance; + Pattern innerPattern; // Parse special percentage syntax if (token.matches("[0-9]+(\\.[0-9]*)?%.*")) { @@ -55,14 +56,14 @@ public class RandomPatternParser extends InputParser { throw new InputParseException("Missing the type after the % symbol for '" + input + "'"); } else { chance = Double.parseDouble(p[0]); - block = blockRegistry.parseFromInput(p[1], context); + innerPattern = worldEdit.getPatternFactory().parseFromInput(p[1], context); } } else { chance = 1; - block = blockRegistry.parseFromInput(token, context); + innerPattern = worldEdit.getPatternFactory().parseFromInput(token, context); } - randomPattern.add(new BlockPattern(block), chance); + randomPattern.add(innerPattern, chance); } return randomPattern; 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 new file mode 100644 index 000000000..db0ee8c38 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/TypeOrStateApplyingPatternParser.java @@ -0,0 +1,79 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.extension.factory.parser.pattern; + +import com.google.common.base.Splitter; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.sk89q.worldedit.WorldEdit; +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.world.block.BlockState; + +import java.util.Map; +import java.util.Set; + + +public class TypeOrStateApplyingPatternParser extends InputParser { + + public TypeOrStateApplyingPatternParser(WorldEdit worldEdit) { + super(worldEdit); + } + + @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("Invalid state format."); + Map statesToSet = Splitter.on(',') + .omitEmptyStrings().trimResults().withKeyValueSeparator('=') + .split(parts[1].substring(0, parts[1].length() - 1)); + 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); + } + } + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/buffer/ExtentBuffer.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/buffer/ExtentBuffer.java new file mode 100644 index 000000000..562f094e5 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/buffer/ExtentBuffer.java @@ -0,0 +1,97 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.extent.buffer; + +import com.google.common.collect.Maps; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.Masks; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; + +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Buffers changes to an {@link Extent} and allows retrieval of the changed blocks, + * without modifying the underlying extent. + */ +public class ExtentBuffer extends AbstractDelegateExtent { + + private final Map buffer = Maps.newHashMap(); + private final Mask mask; + + /** + * Create a new extent buffer that will buffer every change. + * + * @param delegate the delegate extent for {@link Extent#getBlock(BlockVector3)}, etc. calls + */ + public ExtentBuffer(Extent delegate) { + this(delegate, Masks.alwaysTrue()); + } + + /** + * Create a new extent buffer that will buffer changes that meet the criteria + * of the given mask. + * + * @param delegate the delegate extent for {@link Extent#getBlock(BlockVector3)}, etc. calls + * @param mask the mask + */ + public ExtentBuffer(Extent delegate, Mask mask) { + super(delegate); + checkNotNull(delegate); + checkNotNull(mask); + this.mask = mask; + } + + @Override + public BlockState getBlock(BlockVector3 position) { + if (mask.test(position)) { + return getOrDefault(position).toImmutableState(); + } + return super.getBlock(position); + } + + @Override + public BaseBlock getFullBlock(BlockVector3 position) { + if (mask.test(position)) { + return getOrDefault(position); + } + return super.getFullBlock(position); + } + + private BaseBlock getOrDefault(BlockVector3 position) { + return buffer.computeIfAbsent(position, (pos -> getExtent().getFullBlock(pos))); + } + + @Override + public > boolean setBlock(BlockVector3 location, T block) throws WorldEditException { + if (mask.test(location)) { + buffer.put(location, block.toBaseBlock()); + return true; + } + return false; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockStateMask.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockStateMask.java new file mode 100644 index 000000000..69e47039d --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/mask/BlockStateMask.java @@ -0,0 +1,70 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.mask; + +import com.google.common.collect.Maps; +import com.sk89q.worldedit.blocks.Blocks; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockType; + +import javax.annotation.Nullable; +import java.util.Map; + +public class BlockStateMask extends AbstractExtentMask { + + private final Map states; + private final boolean strict; + private Map, Object>> cache = Maps.newHashMap(); + + /** + * Creates a mask that checks if a given block has the desired properties set to the desired value. + * + * @param extent the extent to get blocks from + * @param states the desired states (property -> value) that a block should have to match the mask + * @param strict true to only match blocks that have all properties and values, false to also match blocks that + * do not have the properties (but only fail blocks with the properties but wrong values) + */ + public BlockStateMask(Extent extent, Map states, boolean strict) { + super(extent); + this.states = states; + this.strict = strict; + } + + @Override + public boolean test(BlockVector3 vector) { + BlockState block = getExtent().getBlock(vector); + final Map, Object> checkProps = cache + .computeIfAbsent(block.getBlockType(), (b -> Blocks.resolveProperties(states, b))); + if (strict && checkProps.isEmpty()) { + return false; + } + return checkProps.entrySet().stream() + .allMatch(entry -> block.getState(entry.getKey()) == entry.getValue()); + } + + @Nullable + @Override + public Mask2D toMask2D() { + return null; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/AbstractExtentPattern.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/AbstractExtentPattern.java new file mode 100644 index 000000000..de18b4802 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/AbstractExtentPattern.java @@ -0,0 +1,39 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.pattern; + +import com.sk89q.worldedit.extent.Extent; + +import static com.google.common.base.Preconditions.checkNotNull; + +public abstract class AbstractExtentPattern extends AbstractPattern implements ExtentPattern { + + private final Extent extent; + + public AbstractExtentPattern(Extent extent) { + this.extent = extent; + checkNotNull(extent); + } + + @Override + public Extent getExtent() { + return extent; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/ExtentBufferedCompositePattern.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/ExtentBufferedCompositePattern.java new file mode 100644 index 000000000..b95351e3f --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/ExtentBufferedCompositePattern.java @@ -0,0 +1,66 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.pattern; + +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.buffer.ExtentBuffer; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BaseBlock; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * A pattern that composes multiple patterns consecutively, ensuring that changes from one + * pattern are realized by the subsequent one(s). For best results, use an {@link ExtentBuffer} + * to avoid changing blocks in an underlying extent (e.g. the world). + */ +public class ExtentBufferedCompositePattern extends AbstractExtentPattern { + + private final Pattern[] patterns; + + /** + * Construct a new instance of this pattern. + * + *

Note that all patterns passed which are ExtentPatterns should use the same extent as the one + * passed to this constructor, or block changes may not be realized by those patterns.

+ * + * @param extent the extent to buffer changes to + * @param patterns the patterns to apply, in order + */ + public ExtentBufferedCompositePattern(Extent extent, Pattern... patterns) { + super(extent); + checkArgument(patterns.length != 0, "patterns cannot be empty"); + this.patterns = patterns; + } + + @Override + public BaseBlock apply(BlockVector3 position) { + BaseBlock lastBlock = null; + for (Pattern pattern : patterns) { + lastBlock = pattern.apply(position); + try { + getExtent().setBlock(position, lastBlock); + } catch (WorldEditException ignored) { // buffer doesn't throw + } + } + return lastBlock; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/ExtentPattern.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/ExtentPattern.java new file mode 100644 index 000000000..c0dec41ee --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/ExtentPattern.java @@ -0,0 +1,32 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.pattern; + +import com.sk89q.worldedit.extent.Extent; + +public interface ExtentPattern extends Pattern { + + /** + * Get the extent associated with this pattern. + * + * @return the extent for this pattern + */ + public Extent getExtent(); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RepeatingExtentPattern.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RepeatingExtentPattern.java index 9b16fd439..53e53648b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RepeatingExtentPattern.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/RepeatingExtentPattern.java @@ -28,10 +28,9 @@ import com.sk89q.worldedit.world.block.BaseBlock; /** * Returns the blocks from {@link Extent}, repeating when out of bounds. */ -public class RepeatingExtentPattern extends AbstractPattern { +public class RepeatingExtentPattern extends AbstractExtentPattern { private final BlockVector3 size; - private Extent extent; private BlockVector3 origin; private BlockVector3 offset; @@ -42,31 +41,12 @@ public class RepeatingExtentPattern extends AbstractPattern { * @param offset the offset */ public RepeatingExtentPattern(Extent extent, BlockVector3 origin, BlockVector3 offset) { - setExtent(extent); + super(extent); setOrigin(origin); setOffset(offset); size = extent.getMaximumPoint().subtract(extent.getMinimumPoint()).add(1, 1, 1); } - /** - * Get the extent. - * - * @return the extent - */ - public Extent getExtent() { - return extent; - } - - /** - * Set the extent. - * - * @param extent the extent - */ - public void setExtent(Extent extent) { - checkNotNull(extent); - this.extent = extent; - } - /** * Get the offset. * @@ -111,7 +91,7 @@ public class RepeatingExtentPattern extends AbstractPattern { int x = Math.abs(base.getBlockX()) % size.getBlockX(); int y = Math.abs(base.getBlockY()) % size.getBlockY(); int z = Math.abs(base.getBlockZ()) % size.getBlockZ(); - return extent.getFullBlock(BlockVector3.at(x, y, z).add(origin)); + return getExtent().getFullBlock(BlockVector3.at(x, y, z).add(origin)); } } 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 new file mode 100644 index 000000000..1ed40bffb --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/StateApplyingPattern.java @@ -0,0 +1,54 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.pattern; + +import com.google.common.collect.Maps; +import com.sk89q.worldedit.extent.Extent; +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; +import java.util.Map.Entry; + +import static com.sk89q.worldedit.blocks.Blocks.resolveProperties; + +public class StateApplyingPattern extends AbstractExtentPattern { + + private final Map states; + private Map, Object>> cache = Maps.newHashMap(); + + public StateApplyingPattern(Extent extent, Map statesToSet) { + super(extent); + this.states = statesToSet; + } + + @Override + public BaseBlock apply(BlockVector3 position) { + BlockState block = getExtent().getBlock(position); + for (Entry, Object> entry : cache + .computeIfAbsent(block.getBlockType(), (b -> resolveProperties(states, b))).entrySet()) { + block = block.with(entry.getKey(), entry.getValue()); + } + return block.toBaseBlock(); + } +} 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 new file mode 100644 index 000000000..dc9951805 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/TypeApplyingPattern.java @@ -0,0 +1,52 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.pattern; + +import com.sk89q.worldedit.extent.Extent; +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 java.util.Map.Entry; + +/** + * Applies a block type while retaining all possible states. + */ +public class TypeApplyingPattern extends AbstractExtentPattern { + private final BlockState blockState; + + public TypeApplyingPattern(Extent extent, BlockState blockState) { + super(extent); + this.blockState = blockState; + } + + @Override + public BaseBlock apply(BlockVector3 position) { + BlockState oldBlock = getExtent().getBlock(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(); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/WaterloggedRemover.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/WaterloggedRemover.java new file mode 100644 index 000000000..51f8c0f67 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/pattern/WaterloggedRemover.java @@ -0,0 +1,47 @@ +/* + * 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 Lesser 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 Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.pattern; + +import com.sk89q.worldedit.extent.Extent; +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.BlockTypes; + +/** + * Removes the waterlogged state from blocks if possible. If not possible, returns air. + */ +public class WaterloggedRemover extends AbstractExtentPattern { + + public WaterloggedRemover(Extent extent) { + super(extent); + } + + @Override + public BaseBlock apply(BlockVector3 position) { + BaseBlock block = getExtent().getFullBlock(position); + @SuppressWarnings("unchecked") + Property prop = (Property) block.getBlockType().getPropertyMap().getOrDefault("waterlogged", null); + if (prop != null) { + return block.with(prop, false); + } + return BlockTypes.AIR.getDefaultState().toBaseBlock(); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java index d60c96985..b9e08aab8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java @@ -32,8 +32,8 @@ public final class BlockCategories { public static final BlockCategory BIRCH_LOGS = register("minecraft:birch_logs"); public static final BlockCategory BUTTONS = register("minecraft:buttons"); public static final BlockCategory CARPETS = register("minecraft:carpets"); - public static final BlockCategory CORAL = register("minecraft:coral"); - public static final BlockCategory CORAL_PLANTS = register("minecraft:coral_plants"); + public static final BlockCategory CORALS = register("minecraft:corals"); + public static final BlockCategory CORAL_BLOCKS = register("minecraft:coral_blocks"); public static final BlockCategory DARK_OAK_LOGS = register("minecraft:dark_oak_logs"); public static final BlockCategory DOORS = register("minecraft:doors"); public static final BlockCategory ENDERMAN_HOLDABLE = register("minecraft:enderman_holdable");