Skip to content

Commit

Permalink
Allow copy/pasting biomes.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
wizjany committed Apr 5, 2019
1 parent fa8139f commit 3bdd434
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 11 deletions.
Expand Up @@ -48,6 +48,7 @@
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;

Expand Down Expand Up @@ -75,19 +76,21 @@ public ClipboardCommands(WorldEdit worldEdit) {
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);
}
Expand Down Expand Up @@ -121,6 +124,8 @@ public void cut(Player player, LocalSession session, EditSession editSession,
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);
}
Expand All @@ -133,33 +138,42 @@ public void cut(Player player, LocalSession session, EditSession editSession,
@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 [<mask>] 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
)
@CommandPermissions("worldedit.clipboard.paste")
@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) {
Expand Down
Expand Up @@ -115,6 +115,9 @@ public Operation copyTo(Extent target) {
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;
}

Expand Down
Expand Up @@ -161,6 +161,11 @@ public <B extends BlockStateHolder<B>> boolean setBlock(BlockVector3 position, B
}
}

@Override
public boolean hasBiomes() {
return biomes != null;
}

@Override
public BiomeType getBiome(BlockVector2 position) {
if (biomes != null
Expand Down
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
}
@@ -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);
}
}
Expand Up @@ -28,17 +28,24 @@
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;
Expand All @@ -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;
Expand Down Expand Up @@ -222,6 +230,27 @@ public void setRemovingEntities(boolean 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.
*
Expand Down Expand Up @@ -254,6 +283,25 @@ public Operation resume(RunContext run) throws WorldEditException {

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) {
ExtentEntityCopy entityCopy = new ExtentEntityCopy(from.toVector3(), destination, to.toVector3(), currentTransform);
entityCopy.setRemoving(removingEntities);
Expand All @@ -263,10 +311,10 @@ public Operation resume(RunContext run) throws WorldEditException {
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;
}
Expand Down
Expand Up @@ -25,6 +25,9 @@
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;
Expand All @@ -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.
Expand All @@ -67,6 +74,19 @@ public PasteBuilder to(BlockVector3 to) {
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.
*
Expand All @@ -77,6 +97,29 @@ public PasteBuilder ignoreAirBlocks(boolean ignoreAirBlocks) {
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.
*
Expand All @@ -87,8 +130,13 @@ public Operation build() {
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;
}

Expand Down

0 comments on commit 3bdd434

Please sign in to comment.