From af1af43ac177da0398fdef5a561a307ba12ca1b6 Mon Sep 17 00:00:00 2001 From: wizjany Date: Wed, 3 Apr 2019 20:57:51 -0400 Subject: [PATCH] Allow copy/pasting biomes. Copy takes a -b flag to copy biomes. Paste takes a -b flag to paste biomes (if available). This allows flexibility to create/load schematics with/without biomes (when schematic biome support is added). Also added a -m mask flag to paste to set a source mask, and a -e flag to skip pasting entities if they are loaded. --- .../worldedit/command/ClipboardCommands.java | 28 +++++-- .../command/FlattenedClipboardTransform.java | 3 + .../extent/clipboard/BlockArrayClipboard.java | 5 ++ .../worldedit/extent/clipboard/Clipboard.java | 12 +++ .../function/biome/ExtentBiomeCopy.java | 73 +++++++++++++++++++ .../function/operation/ForwardExtentCopy.java | 54 +++++++++++++- .../sk89q/worldedit/session/PasteBuilder.java | 50 ++++++++++++- 7 files changed, 214 insertions(+), 11 deletions(-) create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/function/biome/ExtentBiomeCopy.java diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java index 0f61f0d2a..52cb67be3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ClipboardCommands.java @@ -48,6 +48,7 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionSelector; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.session.PasteBuilder; import com.sk89q.worldedit.util.command.binding.Switch; import com.sk89q.worldedit.util.command.parametric.Optional; @@ -75,19 +76,21 @@ public class ClipboardCommands { help = "Copy the selection to the clipboard\n" + "Flags:\n" + " -e will also copy entities\n" + - " -m sets a source mask so that excluded blocks become air", + " -m sets a source mask so that excluded blocks become air\n" + + " -b will also copy biomes", min = 0, max = 0 ) @CommandPermissions("worldedit.clipboard.copy") public void copy(Player player, LocalSession session, EditSession editSession, @Selection Region region, @Switch('e') boolean copyEntities, - @Switch('m') Mask mask) throws WorldEditException { + @Switch('m') Mask mask, @Switch('b') boolean copyBiomes) throws WorldEditException { BlockArrayClipboard clipboard = new BlockArrayClipboard(region); clipboard.setOrigin(session.getPlacementPosition(player)); ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint()); copy.setCopyingEntities(copyEntities); + copy.setCopyingBiomes(copyBiomes); if (mask != null) { copy.setSourceMask(mask); } @@ -121,6 +124,8 @@ public class ClipboardCommands { copy.setSourceFunction(new BlockReplace(editSession, leavePattern)); copy.setCopyingEntities(copyEntities); copy.setRemovingEntities(true); + // doesn't really make sense to "cut" biomes? so copy anyway and let them choose when pasting + copy.setCopyingBiomes(true); if (mask != null) { copy.setSourceMask(mask); } @@ -133,14 +138,17 @@ public class ClipboardCommands { @Command( aliases = { "/paste" }, usage = "", - flags = "sao", + flags = "saobem:", desc = "Paste the clipboard's contents", help = "Pastes the clipboard's contents.\n" + "Flags:\n" + " -a skips air blocks\n" + + " -b pastes biomes if available\n" + + " -e skips entities (default is don't skip!)\n" + + " -m [] skips matching blocks in the clipboard\n" + " -o pastes at the original position\n" + - " -s selects the region after pasting", + " -s selects the region after pasting\n", min = 0, max = 0 ) @@ -148,18 +156,24 @@ public class ClipboardCommands { @Logging(PLACEMENT) public void paste(Player player, LocalSession session, EditSession editSession, @Switch('a') boolean ignoreAirBlocks, @Switch('o') boolean atOrigin, - @Switch('s') boolean selectPasted) throws WorldEditException { + @Switch('s') boolean selectPasted, @Switch('e') boolean skipEntities, + @Switch('b') boolean pasteBiomes, @Switch('m') Mask sourceMask) throws WorldEditException { ClipboardHolder holder = session.getClipboard(); Clipboard clipboard = holder.getClipboard(); Region region = clipboard.getRegion(); BlockVector3 to = atOrigin ? clipboard.getOrigin() : session.getPlacementPosition(player); - Operation operation = holder + PasteBuilder builder = holder .createPaste(editSession) .to(to) .ignoreAirBlocks(ignoreAirBlocks) - .build(); + .copyBiomes(pasteBiomes) + .copyEntities(!skipEntities); + if (sourceMask != null) { + builder.maskSource(sourceMask); + } + Operation operation = builder.build(); Operations.completeLegacy(operation); if (selectPasted) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java index 204f062ec..464e1ad97 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java @@ -115,6 +115,9 @@ class FlattenedClipboardTransform { BlockTransformExtent extent = new BlockTransformExtent(original, transform); ForwardExtentCopy copy = new ForwardExtentCopy(extent, original.getRegion(), original.getOrigin(), target, original.getOrigin()); copy.setTransform(transform); + if (original.hasBiomes()) { + copy.setCopyingBiomes(true); + } return copy; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java index 9d13ea579..e3cea6c89 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java @@ -161,6 +161,11 @@ public class BlockArrayClipboard implements Clipboard { } } + @Override + public boolean hasBiomes() { + return biomes != null; + } + @Override public BiomeType getBiome(BlockVector2 position) { if (biomes != null diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/Clipboard.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/Clipboard.java index e0022d480..aeaf07e85 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/Clipboard.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/Clipboard.java @@ -20,6 +20,7 @@ package com.sk89q.worldedit.extent.clipboard; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; @@ -58,4 +59,15 @@ public interface Clipboard extends Extent { */ void setOrigin(BlockVector3 origin); + /** + * Returns true if the clipboard has biome data. This can be checked since {@link Extent#getBiome(BlockVector2)} + * strongly suggests returning {@link com.sk89q.worldedit.world.biome.BiomeTypes.OCEAN} instead of {@code null} + * if biomes aren't present. However, it might not be desired to set areas to ocean if the clipboard is defaulting + * to ocean, instead of having biomes explicitly set. + * + * @return true if the clipboard has biome data set + */ + default boolean hasBiomes() { + return false; + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/biome/ExtentBiomeCopy.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/biome/ExtentBiomeCopy.java new file mode 100644 index 000000000..43dbf984d --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/biome/ExtentBiomeCopy.java @@ -0,0 +1,73 @@ +/* + * 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.biome; + +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.FlatRegionFunction; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.transform.Transform; +import com.sk89q.worldedit.world.biome.BiomeType; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Copies the biome from one extent to another. + */ +public class ExtentBiomeCopy implements FlatRegionFunction { + + private final Extent source; + private final Extent destination; + private final BlockVector2 from; + private final BlockVector2 to; + private final Transform transform; + + /** + * Make a new biome copy. + * + * @param source the source extent + * @param from the source offset + * @param destination the destination extent + * @param to the destination offset + * @param transform a transform to apply to positions (after source offset, before destination offset) + */ + public ExtentBiomeCopy(Extent source, BlockVector2 from, Extent destination, BlockVector2 to, Transform transform) { + checkNotNull(source); + checkNotNull(from); + checkNotNull(destination); + checkNotNull(to); + checkNotNull(transform); + this.source = source; + this.from = from; + this.destination = destination; + this.to = to; + this.transform = transform; + } + + @Override + public boolean apply(BlockVector2 position) throws WorldEditException { + BiomeType biome = source.getBiome(position); + BlockVector2 orig = position.subtract(from); + BlockVector2 transformed = transform.apply(orig.toVector3(0)).toVector2().toBlockPoint(); + + return destination.setBiome(transformed.add(to), biome); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java index cbf73e857..6ca5ad6a6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java @@ -28,17 +28,24 @@ import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.entity.metadata.EntityProperties; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.CombinedRegionFunction; +import com.sk89q.worldedit.function.FlatRegionFunction; +import com.sk89q.worldedit.function.FlatRegionMaskingFilter; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.RegionMaskingFilter; +import com.sk89q.worldedit.function.biome.ExtentBiomeCopy; import com.sk89q.worldedit.function.block.ExtentBlockCopy; import com.sk89q.worldedit.function.entity.ExtentEntityCopy; import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.Mask2D; import com.sk89q.worldedit.function.mask.Masks; import com.sk89q.worldedit.function.visitor.EntityVisitor; +import com.sk89q.worldedit.function.visitor.FlatRegionVisitor; import com.sk89q.worldedit.function.visitor.RegionVisitor; +import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.transform.Identity; import com.sk89q.worldedit.math.transform.Transform; +import com.sk89q.worldedit.regions.FlatRegion; import com.sk89q.worldedit.regions.Region; import java.util.List; @@ -61,6 +68,7 @@ public class ForwardExtentCopy implements Operation { private Mask sourceMask = Masks.alwaysTrue(); private boolean removingEntities; private boolean copyingEntities = true; // default to true for backwards compatibility, sort of + private boolean copyingBiomes; private RegionFunction sourceFunction = null; private Transform transform = new Identity(); private Transform currentTransform = null; @@ -222,6 +230,27 @@ public class ForwardExtentCopy implements Operation { this.removingEntities = removingEntities; } + /** + * Return whether biomes should be copied along with blocks. + * + * @return true if copying biomes + */ + public boolean isCopyingBiomes() { + return copyingBiomes; + } + + /** + * Set whether biomes should be copies along with blocks. + * + * @param copyingBiomes true if copying + */ + public void setCopyingBiomes(boolean copyingBiomes) { + if (copyingBiomes && !(region instanceof FlatRegion)) { + throw new UnsupportedOperationException("Can't copy biomes from region that doesn't implement FlatRegion"); + } + this.copyingBiomes = copyingBiomes; + } + /** * Get the number of affected objects. * @@ -254,6 +283,25 @@ public class ForwardExtentCopy implements Operation { lastVisitor = blockVisitor; + if (!copyingBiomes && !copyingEntities) { + return new DelegateOperation(this, blockVisitor); + } + + List ops = Lists.newArrayList(blockVisitor); + + if (copyingBiomes && region instanceof FlatRegion) { // double-check here even though we checked before + ExtentBiomeCopy biomeCopy = new ExtentBiomeCopy(source, from.toBlockVector2(), + destination, to.toBlockVector2(), currentTransform); + Mask2D biomeMask = sourceMask.toMask2D(); + if (biomeMask != null) { + FlatRegionMaskingFilter filteredBiomeCopy = new FlatRegionMaskingFilter(biomeMask, biomeCopy); + FlatRegionVisitor biomeVisitor = new FlatRegionVisitor(((FlatRegion) region), filteredBiomeCopy); + ops.add(biomeVisitor); + } else { + ops.add(new FlatRegionVisitor(((FlatRegion) region), biomeCopy)); + } + } + if (copyingEntities) { ExtentEntityCopy entityCopy = new ExtentEntityCopy(from.toVector3(), destination, to.toVector3(), currentTransform); entityCopy.setRemoving(removingEntities); @@ -263,10 +311,10 @@ public class ForwardExtentCopy implements Operation { return properties != null && !properties.isPasteable(); }); EntityVisitor entityVisitor = new EntityVisitor(entities.iterator(), entityCopy); - return new DelegateOperation(this, new OperationQueue(blockVisitor, entityVisitor)); - } else { - return new DelegateOperation(this, blockVisitor); + ops.add(entityVisitor); } + + return new DelegateOperation(this, new OperationQueue(ops)); } else { return null; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java index d50a277ac..c46939661 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/session/PasteBuilder.java @@ -25,6 +25,9 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.transform.BlockTransformExtent; import com.sk89q.worldedit.function.mask.ExistingBlockMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.MaskIntersection; +import com.sk89q.worldedit.function.mask.Masks; import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.math.BlockVector3; @@ -39,8 +42,12 @@ public class PasteBuilder { private final Transform transform; private final Extent targetExtent; + private Mask sourceMask = Masks.alwaysTrue(); + private BlockVector3 to = BlockVector3.ZERO; private boolean ignoreAirBlocks; + private boolean copyEntities = true; // default because it used to be this way + private boolean copyBiomes; /** * Create a new instance. @@ -67,6 +74,19 @@ public class PasteBuilder { return this; } + /** + * Set a custom mask of blocks to ignore from the source. + * This provides a more flexible alternative to {@link #ignoreAirBlocks(boolean)}, for example + * one might want to ignore structure void if copying a Minecraft Structure, etc. + * + * @param sourceMask + * @return this builder instance + */ + public PasteBuilder maskSource(Mask sourceMask) { + this.sourceMask = sourceMask; + return this; + } + /** * Set whether air blocks in the source are skipped over when pasting. * @@ -77,6 +97,29 @@ public class PasteBuilder { return this; } + /** + * Set whether the copy should include source entities. + * Note that this is true by default for legacy reasons. + * + * @param copyEntities + * @return this builder instance + */ + public PasteBuilder copyEntities(boolean copyEntities) { + this.copyEntities = copyEntities; + return this; + } + + /** + * Set whether the copy should include source biomes (if available). + * + * @param copyBiomes + * @return this builder instance + */ + public PasteBuilder copyBiomes(boolean copyBiomes) { + this.copyBiomes = copyBiomes; + return this; + } + /** * Build the operation. * @@ -87,8 +130,13 @@ public class PasteBuilder { ForwardExtentCopy copy = new ForwardExtentCopy(extent, clipboard.getRegion(), clipboard.getOrigin(), targetExtent, to); copy.setTransform(transform); if (ignoreAirBlocks) { - copy.setSourceMask(new ExistingBlockMask(clipboard)); + copy.setSourceMask(sourceMask == Masks.alwaysTrue() ? new ExistingBlockMask(clipboard) + : new MaskIntersection(sourceMask, new ExistingBlockMask(clipboard))); + } else { + copy.setSourceMask(sourceMask); } + copy.setCopyingEntities(copyEntities); + copy.setCopyingBiomes(copyBiomes && clipboard.hasBiomes()); return copy; }