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.
This commit is contained in:
wizjany 2019-04-03 20:57:51 -04:00 committed by Matthew Miller
parent 26511bcc25
commit af1af43ac1
7 changed files with 214 additions and 11 deletions

View File

@ -48,6 +48,7 @@ import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionSelector; import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.session.ClipboardHolder; 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.binding.Switch;
import com.sk89q.worldedit.util.command.parametric.Optional; import com.sk89q.worldedit.util.command.parametric.Optional;
@ -75,19 +76,21 @@ public class ClipboardCommands {
help = "Copy the selection to the clipboard\n" + help = "Copy the selection to the clipboard\n" +
"Flags:\n" + "Flags:\n" +
" -e will also copy entities\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, min = 0,
max = 0 max = 0
) )
@CommandPermissions("worldedit.clipboard.copy") @CommandPermissions("worldedit.clipboard.copy")
public void copy(Player player, LocalSession session, EditSession editSession, public void copy(Player player, LocalSession session, EditSession editSession,
@Selection Region region, @Switch('e') boolean copyEntities, @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); BlockArrayClipboard clipboard = new BlockArrayClipboard(region);
clipboard.setOrigin(session.getPlacementPosition(player)); clipboard.setOrigin(session.getPlacementPosition(player));
ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint()); ForwardExtentCopy copy = new ForwardExtentCopy(editSession, region, clipboard, region.getMinimumPoint());
copy.setCopyingEntities(copyEntities); copy.setCopyingEntities(copyEntities);
copy.setCopyingBiomes(copyBiomes);
if (mask != null) { if (mask != null) {
copy.setSourceMask(mask); copy.setSourceMask(mask);
} }
@ -121,6 +124,8 @@ public class ClipboardCommands {
copy.setSourceFunction(new BlockReplace(editSession, leavePattern)); copy.setSourceFunction(new BlockReplace(editSession, leavePattern));
copy.setCopyingEntities(copyEntities); copy.setCopyingEntities(copyEntities);
copy.setRemovingEntities(true); 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) { if (mask != null) {
copy.setSourceMask(mask); copy.setSourceMask(mask);
} }
@ -133,14 +138,17 @@ public class ClipboardCommands {
@Command( @Command(
aliases = { "/paste" }, aliases = { "/paste" },
usage = "", usage = "",
flags = "sao", flags = "saobem:",
desc = "Paste the clipboard's contents", desc = "Paste the clipboard's contents",
help = help =
"Pastes the clipboard's contents.\n" + "Pastes the clipboard's contents.\n" +
"Flags:\n" + "Flags:\n" +
" -a skips air blocks\n" + " -a skips air blocks\n" +
" -b pastes biomes if available\n" +
" -e skips entities (default is don't skip!)\n" +
" -m [<mask>] skips matching blocks in the clipboard\n" +
" -o pastes at the original position\n" + " -o pastes at the original position\n" +
" -s selects the region after pasting", " -s selects the region after pasting\n",
min = 0, min = 0,
max = 0 max = 0
) )
@ -148,18 +156,24 @@ public class ClipboardCommands {
@Logging(PLACEMENT) @Logging(PLACEMENT)
public void paste(Player player, LocalSession session, EditSession editSession, public void paste(Player player, LocalSession session, EditSession editSession,
@Switch('a') boolean ignoreAirBlocks, @Switch('o') boolean atOrigin, @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(); ClipboardHolder holder = session.getClipboard();
Clipboard clipboard = holder.getClipboard(); Clipboard clipboard = holder.getClipboard();
Region region = clipboard.getRegion(); Region region = clipboard.getRegion();
BlockVector3 to = atOrigin ? clipboard.getOrigin() : session.getPlacementPosition(player); BlockVector3 to = atOrigin ? clipboard.getOrigin() : session.getPlacementPosition(player);
Operation operation = holder PasteBuilder builder = holder
.createPaste(editSession) .createPaste(editSession)
.to(to) .to(to)
.ignoreAirBlocks(ignoreAirBlocks) .ignoreAirBlocks(ignoreAirBlocks)
.build(); .copyBiomes(pasteBiomes)
.copyEntities(!skipEntities);
if (sourceMask != null) {
builder.maskSource(sourceMask);
}
Operation operation = builder.build();
Operations.completeLegacy(operation); Operations.completeLegacy(operation);
if (selectPasted) { if (selectPasted) {

View File

@ -115,6 +115,9 @@ class FlattenedClipboardTransform {
BlockTransformExtent extent = new BlockTransformExtent(original, transform); BlockTransformExtent extent = new BlockTransformExtent(original, transform);
ForwardExtentCopy copy = new ForwardExtentCopy(extent, original.getRegion(), original.getOrigin(), target, original.getOrigin()); ForwardExtentCopy copy = new ForwardExtentCopy(extent, original.getRegion(), original.getOrigin(), target, original.getOrigin());
copy.setTransform(transform); copy.setTransform(transform);
if (original.hasBiomes()) {
copy.setCopyingBiomes(true);
}
return copy; return copy;
} }

View File

@ -161,6 +161,11 @@ public class BlockArrayClipboard implements Clipboard {
} }
} }
@Override
public boolean hasBiomes() {
return biomes != null;
}
@Override @Override
public BiomeType getBiome(BlockVector2 position) { public BiomeType getBiome(BlockVector2 position) {
if (biomes != null if (biomes != null

View File

@ -20,6 +20,7 @@
package com.sk89q.worldedit.extent.clipboard; package com.sk89q.worldedit.extent.clipboard;
import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Region;
@ -58,4 +59,15 @@ public interface Clipboard extends Extent {
*/ */
void setOrigin(BlockVector3 origin); 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;
}
} }

View File

@ -0,0 +1,73 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -28,17 +28,24 @@ import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.entity.metadata.EntityProperties; import com.sk89q.worldedit.entity.metadata.EntityProperties;
import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.CombinedRegionFunction; 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.RegionFunction;
import com.sk89q.worldedit.function.RegionMaskingFilter; 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.block.ExtentBlockCopy;
import com.sk89q.worldedit.function.entity.ExtentEntityCopy; import com.sk89q.worldedit.function.entity.ExtentEntityCopy;
import com.sk89q.worldedit.function.mask.Mask; 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.mask.Masks;
import com.sk89q.worldedit.function.visitor.EntityVisitor; 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.function.visitor.RegionVisitor;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.transform.Identity; import com.sk89q.worldedit.math.transform.Identity;
import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.regions.FlatRegion;
import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Region;
import java.util.List; import java.util.List;
@ -61,6 +68,7 @@ public class ForwardExtentCopy implements Operation {
private Mask sourceMask = Masks.alwaysTrue(); private Mask sourceMask = Masks.alwaysTrue();
private boolean removingEntities; private boolean removingEntities;
private boolean copyingEntities = true; // default to true for backwards compatibility, sort of private boolean copyingEntities = true; // default to true for backwards compatibility, sort of
private boolean copyingBiomes;
private RegionFunction sourceFunction = null; private RegionFunction sourceFunction = null;
private Transform transform = new Identity(); private Transform transform = new Identity();
private Transform currentTransform = null; private Transform currentTransform = null;
@ -222,6 +230,27 @@ public class ForwardExtentCopy implements Operation {
this.removingEntities = removingEntities; 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. * Get the number of affected objects.
* *
@ -254,6 +283,25 @@ public class ForwardExtentCopy implements Operation {
lastVisitor = blockVisitor; lastVisitor = blockVisitor;
if (!copyingBiomes && !copyingEntities) {
return new DelegateOperation(this, blockVisitor);
}
List<Operation> 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) { if (copyingEntities) {
ExtentEntityCopy entityCopy = new ExtentEntityCopy(from.toVector3(), destination, to.toVector3(), currentTransform); ExtentEntityCopy entityCopy = new ExtentEntityCopy(from.toVector3(), destination, to.toVector3(), currentTransform);
entityCopy.setRemoving(removingEntities); entityCopy.setRemoving(removingEntities);
@ -263,10 +311,10 @@ public class ForwardExtentCopy implements Operation {
return properties != null && !properties.isPasteable(); return properties != null && !properties.isPasteable();
}); });
EntityVisitor entityVisitor = new EntityVisitor(entities.iterator(), entityCopy); EntityVisitor entityVisitor = new EntityVisitor(entities.iterator(), entityCopy);
return new DelegateOperation(this, new OperationQueue(blockVisitor, entityVisitor)); ops.add(entityVisitor);
} else {
return new DelegateOperation(this, blockVisitor);
} }
return new DelegateOperation(this, new OperationQueue(ops));
} else { } else {
return null; return null;
} }

View File

@ -25,6 +25,9 @@ import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.transform.BlockTransformExtent; import com.sk89q.worldedit.extent.transform.BlockTransformExtent;
import com.sk89q.worldedit.function.mask.ExistingBlockMask; 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.ForwardExtentCopy;
import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.BlockVector3;
@ -39,8 +42,12 @@ public class PasteBuilder {
private final Transform transform; private final Transform transform;
private final Extent targetExtent; private final Extent targetExtent;
private Mask sourceMask = Masks.alwaysTrue();
private BlockVector3 to = BlockVector3.ZERO; private BlockVector3 to = BlockVector3.ZERO;
private boolean ignoreAirBlocks; private boolean ignoreAirBlocks;
private boolean copyEntities = true; // default because it used to be this way
private boolean copyBiomes;
/** /**
* Create a new instance. * Create a new instance.
@ -67,6 +74,19 @@ public class PasteBuilder {
return this; 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. * Set whether air blocks in the source are skipped over when pasting.
* *
@ -77,6 +97,29 @@ public class PasteBuilder {
return this; 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. * Build the operation.
* *
@ -87,8 +130,13 @@ public class PasteBuilder {
ForwardExtentCopy copy = new ForwardExtentCopy(extent, clipboard.getRegion(), clipboard.getOrigin(), targetExtent, to); ForwardExtentCopy copy = new ForwardExtentCopy(extent, clipboard.getRegion(), clipboard.getOrigin(), targetExtent, to);
copy.setTransform(transform); copy.setTransform(transform);
if (ignoreAirBlocks) { 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; return copy;
} }