diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java index e2509e47e..8461826f9 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java @@ -67,6 +67,14 @@ public class BukkitServerInterface implements MultiUserPlatform { return BukkitRegistries.getInstance(); } + @Override + public int getDataVersion() { + if (plugin.getBukkitImplAdapter() != null) { + return plugin.getBukkitImplAdapter().getDataVersion(); + } + return 0; + } + @Override public boolean isValidMobType(String type) { final EntityType entityType = EntityType.fromName(type); diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java index 1a4329a4f..9ac0fe8de 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java @@ -40,6 +40,13 @@ import javax.annotation.Nullable; */ public interface BukkitImplAdapter { + /** + * Get the Minecraft data version for the current world data. + * + * @return the data version + */ + int getDataVersion(); + /** * Get the block at the given location. * diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1$1.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1$1.class index efa78dacc..68556cab9 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1$1.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1$1.class differ diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class index 31b547024..de8e81b01 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R1.class differ diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2$1.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2$1.class index 97a809b53..e8e38065a 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2$1.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2$1.class differ diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2.class index 8a784a737..22e2b2ba3 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2.class differ diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2$1.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2$1.class index 0c277042c..86e5c9d33 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2$1.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2$1.class differ diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2.class index b2f5164fa..d2291fb60 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_13_R2_2.class differ diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_14_R1$1.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_14_R1$1.class index 185607a49..66d9acaf1 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_14_R1$1.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_14_R1$1.class differ diff --git a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_14_R1.class b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_14_R1.class index 518d49437..da36d279f 100644 Binary files a/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_14_R1.class and b/worldedit-bukkit/src/main/resources/com/sk89q/worldedit/bukkit/adapter/impl/Spigot_v1_14_R1.class differ diff --git a/worldedit-core/src/main/java/com/sk89q/jnbt/CompoundTagBuilder.java b/worldedit-core/src/main/java/com/sk89q/jnbt/CompoundTagBuilder.java index b0e873c0d..6b5776619 100644 --- a/worldedit-core/src/main/java/com/sk89q/jnbt/CompoundTagBuilder.java +++ b/worldedit-core/src/main/java/com/sk89q/jnbt/CompoundTagBuilder.java @@ -181,6 +181,18 @@ public class CompoundTagBuilder { return put(key, new StringTag(value)); } + /** + * Remove the given key from the compound tag. Does nothing if the key doesn't exist. + * + * @param key the key + * @return this object + */ + public CompoundTagBuilder remove(String key) { + checkNotNull(key); + entries.remove(key); + return this; + } + /** * Put all the entries from the given map into this map. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index ebc2e9e5a..d187723ba 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -59,6 +59,7 @@ import com.sk89q.worldedit.world.block.BlockTypes; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.param.Arg; +import org.enginehub.piston.annotation.param.ArgFlag; import org.enginehub.piston.annotation.param.Switch; import static com.google.common.base.Preconditions.checkNotNull; @@ -145,8 +146,14 @@ public class BrushCommands { public void clipboardBrush(Player player, LocalSession session, @Switch(name = 'a', desc = "Don't paste air from the clipboard") boolean ignoreAir, - @Switch(name = 'p', desc = "Paste using clipboard origin, instead of being centered at the target location") - boolean usingOrigin) throws WorldEditException { + @Switch(name = 'o', desc = "Paste using clipboard origin, instead of being centered at the target location") + boolean usingOrigin, + @Switch(name = 'e', desc = "Paste entities if available") + boolean pasteEntities, + @Switch(name = 'b', desc = "Paste biomes if available") + boolean pasteBiomes, + @ArgFlag(name = 'm', desc = "Skip blocks matching this mask", def = "") + Mask sourceMask) throws WorldEditException { ClipboardHolder holder = session.getClipboard(); Clipboard clipboard = holder.getClipboard(); @@ -157,7 +164,7 @@ public class BrushCommands { worldEdit.checkMaxBrushRadius(size.getBlockZ() / 2D - 1); BrushTool tool = session.getBrushTool(player.getItemInHand(HandSide.MAIN_HAND).getType()); - tool.setBrush(new ClipboardBrush(holder, ignoreAir, usingOrigin), "worldedit.brush.clipboard"); + tool.setBrush(new ClipboardBrush(holder, ignoreAir, usingOrigin, pasteEntities, pasteBiomes, sourceMask), "worldedit.brush.clipboard"); player.print("Clipboard brush shape equipped."); } 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 db5c5fa63..01d1a1fb9 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 @@ -19,6 +19,7 @@ package com.sk89q.worldedit.command; +import com.google.common.collect.Lists; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEditException; @@ -49,6 +50,8 @@ import org.enginehub.piston.annotation.param.Arg; import org.enginehub.piston.annotation.param.ArgFlag; import org.enginehub.piston.annotation.param.Switch; +import java.util.List; + import static com.sk89q.worldedit.command.util.Logging.LogMode.PLACEMENT; import static com.sk89q.worldedit.command.util.Logging.LogMode.REGION; @@ -73,19 +76,24 @@ public class ClipboardCommands { @Selection Region region, @Switch(name = 'e', desc = "Also copy entities") boolean copyEntities, + @Switch(name = 'b', desc = "Also copy biomes") + boolean copyBiomes, @ArgFlag(name = 'm', desc = "Set the exclude mask, matching blocks become air", def = "") Mask mask) 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); } Operations.completeLegacy(copy); session.setClipboard(new ClipboardHolder(clipboard)); - player.print(region.getArea() + " block(s) were copied."); + List messages = Lists.newArrayList(); + copy.addStatusMessages(messages); + messages.forEach(player::print); } @Command( @@ -101,6 +109,8 @@ public class ClipboardCommands { Pattern leavePattern, @Switch(name = 'e', desc = "Also cut entities") boolean copyEntities, + @Switch(name = 'b', desc = "Also copy biomes, source biomes are unaffected") + boolean copyBiomes, @ArgFlag(name = 'm', desc = "Set the exclude mask, matching blocks become air", def = "") Mask mask) throws WorldEditException { @@ -110,13 +120,16 @@ public class ClipboardCommands { copy.setSourceFunction(new BlockReplace(editSession, leavePattern)); copy.setCopyingEntities(copyEntities); copy.setRemovingEntities(true); + copy.setCopyingBiomes(copyBiomes); if (mask != null) { copy.setSourceMask(mask); } Operations.completeLegacy(copy); session.setClipboard(new ClipboardHolder(clipboard)); - player.print(region.getArea() + " block(s) were cut."); + List messages = Lists.newArrayList(); + copy.addStatusMessages(messages); + messages.forEach(player::print); } @Command( @@ -131,7 +144,13 @@ public class ClipboardCommands { @Switch(name = 'o', desc = "Paste at the original position") boolean atOrigin, @Switch(name = 's', desc = "Select the region after pasting") - boolean selectPasted) throws WorldEditException { + boolean selectPasted, + @Switch(name = 'e', desc = "Paste entities if available") + boolean pasteEntities, + @Switch(name = 'b', desc = "Paste biomes if available") + boolean pasteBiomes, + @ArgFlag(name = 'm', desc = "Skip blocks matching this mask", def = "") + Mask sourceMask) throws WorldEditException { ClipboardHolder holder = session.getClipboard(); Clipboard clipboard = holder.getClipboard(); @@ -139,10 +158,13 @@ public class ClipboardCommands { BlockVector3 to = atOrigin ? clipboard.getOrigin() : session.getPlacementPosition(player); Operation operation = holder - .createPaste(editSession) - .to(to) - .ignoreAirBlocks(ignoreAirBlocks) - .build(); + .createPaste(editSession) + .to(to) + .ignoreAirBlocks(ignoreAirBlocks) + .copyBiomes(pasteBiomes) + .copyEntities(pasteEntities) + .maskSource(sourceMask) + .build(); Operations.completeLegacy(operation); if (selectPasted) { @@ -156,6 +178,9 @@ public class ClipboardCommands { } player.print("The clipboard has been pasted at " + to); + List messages = Lists.newArrayList(); + operation.addStatusMessages(messages); + messages.forEach(player::print); } @Command( 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/command/tool/brush/ClipboardBrush.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/ClipboardBrush.java index d6abe3cc9..bbb489242 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/ClipboardBrush.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/brush/ClipboardBrush.java @@ -22,6 +22,7 @@ package com.sk89q.worldedit.command.tool.brush; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.pattern.Pattern; @@ -31,14 +32,30 @@ import com.sk89q.worldedit.session.ClipboardHolder; public class ClipboardBrush implements Brush { - private ClipboardHolder holder; - private boolean ignoreAirBlocks; - private boolean usingOrigin; + private final ClipboardHolder holder; + private final boolean ignoreAirBlocks; + private final boolean usingOrigin; + private final boolean pasteEntities; + private final boolean pasteBiomes; + private final Mask sourceMask; public ClipboardBrush(ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin) { this.holder = holder; this.ignoreAirBlocks = ignoreAirBlocks; this.usingOrigin = usingOrigin; + this.pasteBiomes = false; + this.pasteEntities = false; + this.sourceMask = null; + } + + public ClipboardBrush(ClipboardHolder holder, boolean ignoreAirBlocks, boolean usingOrigin, boolean pasteEntities, + boolean pasteBiomes, Mask sourceMask) { + this.holder = holder; + this.ignoreAirBlocks = ignoreAirBlocks; + this.usingOrigin = usingOrigin; + this.pasteEntities = pasteEntities; + this.pasteBiomes = pasteBiomes; + this.sourceMask = sourceMask; } @Override @@ -51,6 +68,9 @@ public class ClipboardBrush implements Brush { .createPaste(editSession) .to(usingOrigin ? position : position.subtract(centerOffset)) .ignoreAirBlocks(ignoreAirBlocks) + .copyEntities(pasteEntities) + .copyBiomes(pasteBiomes) + .maskSource(sourceMask) .build(); Operations.completeLegacy(operation); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java index ace903fe0..79fac2c57 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java @@ -46,6 +46,13 @@ public interface Platform { */ Registries getRegistries(); + /** + * Gets the Minecraft data version being used by the platform. + * + * @return the data version + */ + int getDataVersion(); + /** * Checks if a mob type is valid. * 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..029d25052 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,12 +161,20 @@ public class BlockArrayClipboard implements Clipboard { } } + @Override + public boolean hasBiomes() { + return biomes != null; + } + @Override public BiomeType getBiome(BlockVector2 position) { if (biomes != null && position.containedWithin(getMinimumPoint().toBlockVector2(), getMaximumPoint().toBlockVector2())) { BlockVector2 v = position.subtract(region.getMinimumPoint().toBlockVector2()); - return biomes[v.getBlockX()][v.getBlockZ()]; + BiomeType biomeType = biomes[v.getBlockX()][v.getBlockZ()]; + if (biomeType != null) { + return biomeType; + } } return BiomeTypes.OCEAN; 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/extent/clipboard/io/MCEditSchematicReader.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/MCEditSchematicReader.java index 3997f28c7..d21533e20 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/MCEditSchematicReader.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/MCEditSchematicReader.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.extent.clipboard.io; +import com.google.common.collect.ImmutableList; import com.sk89q.jnbt.ByteArrayTag; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.IntTag; @@ -32,8 +33,13 @@ import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.legacycompat.EntityNBTCompatibilityHandler; +import com.sk89q.worldedit.extent.clipboard.io.legacycompat.FlowerPotCompatibilityHandler; import com.sk89q.worldedit.extent.clipboard.io.legacycompat.NBTCompatibilityHandler; +import com.sk89q.worldedit.extent.clipboard.io.legacycompat.NoteBlockCompatibilityHandler; +import com.sk89q.worldedit.extent.clipboard.io.legacycompat.Pre13HangingCompatibilityHandler; import com.sk89q.worldedit.extent.clipboard.io.legacycompat.SignCompatibilityHandler; +import com.sk89q.worldedit.extent.clipboard.io.legacycompat.SkullBlockCompatibilityHandler; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; @@ -47,10 +53,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import static com.google.common.base.Preconditions.checkNotNull; @@ -59,12 +66,18 @@ import static com.google.common.base.Preconditions.checkNotNull; */ public class MCEditSchematicReader extends NBTSchematicReader { - private static final List COMPATIBILITY_HANDLERS = new ArrayList<>(); - - static { - COMPATIBILITY_HANDLERS.add(new SignCompatibilityHandler()); - // TODO Add a handler for skulls, flower pots, note blocks, etc. - } + private static final ImmutableList COMPATIBILITY_HANDLERS + = ImmutableList.of( + new SignCompatibilityHandler(), + new FlowerPotCompatibilityHandler(), + new NoteBlockCompatibilityHandler(), + new SkullBlockCompatibilityHandler() + // TODO - item tags for inventories...? DFUs :> + ); + private static final ImmutableList ENTITY_COMPATIBILITY_HANDLERS + = ImmutableList.of( + new Pre13HangingCompatibilityHandler() + ); private static final Logger log = LoggerFactory.getLogger(MCEditSchematicReader.class); private final NBTInputStream inputStream; @@ -162,65 +175,55 @@ public class MCEditSchematicReader extends NBTSchematicReader { // Need to pull out tile entities List tileEntities = requireTag(schematic, "TileEntities", ListTag.class).getValue(); Map> tileEntitiesMap = new HashMap<>(); + Map blockOverrides = new HashMap<>(); for (Tag tag : tileEntities) { if (!(tag instanceof CompoundTag)) continue; CompoundTag t = (CompoundTag) tag; - int x = 0; - int y = 0; - int z = 0; + int x = t.getInt("x"); + int y = t.getInt("y"); + int z = t.getInt("z"); + String id = t.getString("id"); - Map values = new HashMap<>(); - - for (Map.Entry entry : t.getValue().entrySet()) { - switch (entry.getKey()) { - case "x": - if (entry.getValue() instanceof IntTag) { - x = ((IntTag) entry.getValue()).getValue(); - } - break; - case "y": - if (entry.getValue() instanceof IntTag) { - y = ((IntTag) entry.getValue()).getValue(); - } - break; - case "z": - if (entry.getValue() instanceof IntTag) { - z = ((IntTag) entry.getValue()).getValue(); - } - break; - } - - values.put(entry.getKey(), entry.getValue()); - } + Map values = new HashMap<>(t.getValue()); + values.put("id", new StringTag(convertBlockEntityId(id))); int index = y * width * length + z * width + x; BlockState block = LegacyMapper.getInstance().getBlockFromLegacy(blocks[index], blockData[index]); - if (block != null) { + BlockState newBlock = block; + if (newBlock != null) { for (NBTCompatibilityHandler handler : COMPATIBILITY_HANDLERS) { - if (handler.isAffectedBlock(block)) { - handler.updateNBT(block, values); + if (handler.isAffectedBlock(newBlock)) { + newBlock = handler.updateNBT(block, values); + if (newBlock == null) { + break; + } } } } BlockVector3 vec = BlockVector3.at(x, y, z); tileEntitiesMap.put(vec, values); + if (newBlock != block) { + blockOverrides.put(vec, newBlock); + } } BlockArrayClipboard clipboard = new BlockArrayClipboard(region); clipboard.setOrigin(origin); - // Don't log a torrent of errors - int failedBlockSets = 0; + Set unknownBlocks = new HashSet<>(); for (int x = 0; x < width; ++x) { for (int y = 0; y < height; ++y) { for (int z = 0; z < length; ++z) { int index = y * width * length + z * width + x; BlockVector3 pt = BlockVector3.at(x, y, z); - BlockState state = LegacyMapper.getInstance().getBlockFromLegacy(blocks[index], blockData[index]); + boolean useOverride = blockOverrides.containsKey(pt); + BlockState state = useOverride + ? blockOverrides.get(pt) + : LegacyMapper.getInstance().getBlockFromLegacy(blocks[index], blockData[index]); try { if (state != null) { @@ -229,21 +232,16 @@ public class MCEditSchematicReader extends NBTSchematicReader { } else { clipboard.setBlock(region.getMinimumPoint().add(pt), state); } - } else { - log.warn("Unknown block when pasting schematic: " + blocks[index] + ":" + blockData[index] + ". Please report this issue."); + } else if (!useOverride) { + short block = blocks[index]; + byte data = blockData[index]; + int combined = block << 8 | data; + if (unknownBlocks.add(combined)) { + log.warn("Unknown block when pasting schematic: " + + block + ":" + data + ". Please report this issue."); + } } - } catch (WorldEditException e) { - switch (failedBlockSets) { - case 0: - log.warn("Failed to set block on a Clipboard", e); - break; - case 1: - log.warn("Failed to set block on a Clipboard (again) -- no more messages will be logged", e); - break; - default: - } - - failedBlockSets++; + } catch (WorldEditException ignored) { // BlockArrayClipboard won't throw this } } } @@ -253,8 +251,9 @@ public class MCEditSchematicReader extends NBTSchematicReader { // Entities // ==================================================================== - List entityTags = getTag(schematic, "Entities", ListTag.class).getValue(); - if (entityTags != null) { + ListTag entityList = getTag(schematic, "Entities", ListTag.class); + if (entityList != null) { + List entityTags = entityList.getValue(); for (Tag tag : entityTags) { if (tag instanceof CompoundTag) { CompoundTag compound = (CompoundTag) tag; @@ -264,6 +263,11 @@ public class MCEditSchematicReader extends NBTSchematicReader { if (!id.isEmpty()) { EntityType entityType = EntityTypes.get(id.toLowerCase()); if (entityType != null) { + for (EntityNBTCompatibilityHandler compatibilityHandler : ENTITY_COMPATIBILITY_HANDLERS) { + if (compatibilityHandler.isAffectedEntity(entityType, compound)) { + compound = compatibilityHandler.updateNBT(entityType, compound); + } + } BaseEntity state = new BaseEntity(entityType, compound); clipboard.createEntity(location, state); } else { @@ -279,32 +283,66 @@ public class MCEditSchematicReader extends NBTSchematicReader { private String convertEntityId(String id) { switch(id) { - case "xp_orb": - return "experience_orb"; - case "xp_bottle": - return "experience_bottle"; - case "eye_of_ender_signal": - return "eye_of_ender"; - case "ender_crystal": - return "end_crystal"; - case "fireworks_rocket": - return "firework_rocket"; - case "commandblock_minecart": - return "command_block_minecart"; - case "snowman": - return "snow_golem"; - case "villager_golem": - return "iron_golem"; - case "evocation_fangs": - return "evoker_fangs"; - case "evocation_illager": - return "evoker"; - case "vindication_illager": - return "vindicator"; - case "illusion_illager": - return "illusioner"; + case "AreaEffectCloud": return "area_effect_cloud"; + case "ArmorStand": return "armor_stand"; + case "CaveSpider": return "cave_spider"; + case "MinecartChest": return "chest_minecart"; + case "MinecartCommandBlock": return "commandblock_minecart"; + case "DragonFireball": return "dragon_fireball"; + case "ThrownEgg": return "egg"; + case "EnderCrystal": return "ender_crystal"; + case "EnderDragon": return "ender_dragon"; + case "ThrownEnderpearl": return "ender_pearl"; + case "EyeOfEnderSignal": return "eye_of_ender_signal"; + case "FallingSand": return "falling_block"; + case "FireworksRocketEntity": return "fireworks_rocket"; + case "MinecartFurnace": return "furnace_minecart"; + case "MinecartHopper": return "hopper_minecart"; + case "EntityHorse": return "horse"; + case "ItemFrame": return "item_frame"; + case "LeashKnot": return "leash_knot"; + case "LightningBolt": return "lightning_bolt"; + case "LavaSlime": return "magma_cube"; + case "MinecartRideable": return "minecart"; + case "MushroomCow": return "mooshroom"; + case "Ozelot": return "ocelot"; + case "PolarBear": return "polar_bear"; + case "ThrownPotion": return "potion"; + case "ShulkerBullet": return "shulker_bullet"; + case "SmallFireball": return "small_fireball"; + case "MinecartSpawner": return "spawner_minecart"; + case "SpectralArrow": return "spectral_arrow"; + case "PrimedTnt": return "tnt"; + case "MinecartTNT": return "tnt_minecart"; + case "VillagerGolem": return "villager_golem"; + case "WitherBoss": return "wither"; + case "WitherSkull": return "wither_skull"; + case "ThrownExpBottle": return "xp_bottle"; + case "XPOrb": return "xp_orb"; + case "PigZombie": return "zombie_pigman"; + default: return id; + } + } + + private String convertBlockEntityId(String id) { + switch(id) { + case "Cauldron": return "brewing_stand"; + case "Control": return "command_block"; + case "DLDetector": return "daylight_detector"; + case "Trap": return "dispenser"; + case "EnchantTable": return "enchanting_table"; + case "EndGateway": return "end_gateway"; + case "AirPortal": return "end_portal"; + case "EnderChest": return "ender_chest"; + case "FlowerPot": return "flower_pot"; + case "RecordPlayer": return "jukebox"; + case "MobSpawner": return "mob_spawner"; + case "Music": + case "noteblock": + return "note_block"; + case "Structure": return "structure_block"; + default: return id; } - return id; } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java index 09594e388..556a60570 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java @@ -28,18 +28,28 @@ import com.sk89q.jnbt.ListTag; import com.sk89q.jnbt.NBTInputStream; import com.sk89q.jnbt.NamedTag; import com.sk89q.jnbt.ShortTag; +import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.legacycompat.NBTCompatibilityHandler; +import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.entity.EntityTypes; +import com.sk89q.worldedit.world.storage.NBTConversions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,6 +58,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; @@ -89,6 +100,15 @@ public class SpongeSchematicReader extends NBTSchematicReader { int version = requireTag(schematic, "Version", IntTag.class).getValue(); if (version == 1) { return readVersion1(schematicTag); + } else if (version == 2) { + int dataVersion = requireTag(schematic, "DataVersion", IntTag.class).getValue(); + if (dataVersion > WorldEdit.getInstance().getPlatformManager() + .queryCapability(Capability.WORLD_EDITING).getDataVersion()) { + // maybe should just warn? simple schematics may be compatible still. + throw new IOException("Schematic was made in a newer Minecraft version. Data may be incompatible."); + } + BlockArrayClipboard clip = readVersion1(schematicTag); + return readVersion2(clip, schematicTag); } throw new IOException("This schematic version is currently not supported"); } @@ -126,7 +146,7 @@ public class SpongeSchematicReader extends NBTSchematicReader { int paletteMax = requireTag(schematic, "PaletteMax", IntTag.class).getValue(); Map paletteObject = requireTag(schematic, "Palette", CompoundTag.class).getValue(); if (paletteObject.size() != paletteMax) { - throw new IOException("Differing given palette size to actual size"); + throw new IOException("Block palette size does not match expected size."); } Map palette = new HashMap<>(); @@ -142,7 +162,8 @@ public class SpongeSchematicReader extends NBTSchematicReader { try { state = WorldEdit.getInstance().getBlockFactory().parseFromInput(palettePart, parserContext).toImmutableState(); } catch (InputParseException e) { - throw new IOException("Invalid BlockState in schematic: " + palettePart + ". Are you missing a mod or using a schematic made in a newer version of Minecraft?"); + throw new IOException("Invalid BlockState in palette: " + palettePart + + ". Are you missing a mod or using a schematic made in a newer version of Minecraft?"); } palette.put(id, state); } @@ -168,8 +189,8 @@ public class SpongeSchematicReader extends NBTSchematicReader { int index = 0; int i = 0; - int value = 0; - int varintLength = 0; + int value; + int varintLength; while (i < blocks.length) { value = 0; varintLength = 0; @@ -185,7 +206,7 @@ public class SpongeSchematicReader extends NBTSchematicReader { } i++; } - // index = (y * length + z) * width + x + // index = (y * length * width) + (z * width) + x int y = index / (width * length); int z = (index % (width * length)) / width; int x = (index % (width * length)) % width; @@ -219,6 +240,99 @@ public class SpongeSchematicReader extends NBTSchematicReader { return clipboard; } + private Clipboard readVersion2(BlockArrayClipboard version1, CompoundTag schematicTag) throws IOException { + Map schematic = schematicTag.getValue(); + if (schematic.containsKey("BiomeData")) { + readBiomes(version1, schematic); + } + if (schematic.containsKey("Entities")) { + readEntities(version1, schematic); + } + return version1; + } + + private void readBiomes(BlockArrayClipboard clipboard, Map schematic) throws IOException { + ByteArrayTag dataTag = requireTag(schematic, "BiomeData", ByteArrayTag.class); + IntTag maxTag = requireTag(schematic, "BiomePaletteMax", IntTag.class); + CompoundTag paletteTag = requireTag(schematic, "BiomePalette", CompoundTag.class); + + Map palette = new HashMap<>(); + if (maxTag.getValue() != paletteTag.getValue().size()) { + throw new IOException("Biome palette size does not match expected size."); + } + Map paletteEntries = paletteTag.getValue(); + + for (Entry palettePart : paletteEntries.entrySet()) { + BiomeType biome = BiomeTypes.get(palettePart.getKey()); + if (biome == null) { + log.warn("Unknown biome type :" + palettePart.getKey() + + " in palette. Are you missing a mod or using a schematic made in a newer version of Minecraft?"); + } + Tag idTag = palettePart.getValue(); + if (!(idTag instanceof IntTag)) { + throw new IOException("Biome mapped to non-Int tag."); + } + palette.put(((IntTag) idTag).getValue(), biome); + } + + int width = clipboard.getDimensions().getX(); + + byte[] biomes = dataTag.getValue(); + int biomeIndex = 0; + int biomeJ = 0; + int bVal; + int varIntLength; + BlockVector2 min = clipboard.getMinimumPoint().toBlockVector2(); + while (biomeJ < biomes.length) { + bVal = 0; + varIntLength = 0; + + while (true) { + bVal |= (biomes[biomeJ] & 127) << (varIntLength++ * 7); + if (varIntLength > 5) { + throw new RuntimeException("VarInt too big (probably corrupted data)"); + } + if (((biomes[biomeJ] & 128) != 128)) { + biomeJ++; + break; + } + biomeJ++; + } + int z = biomeIndex / width; + int x = biomeIndex % width; + BiomeType type = palette.get(bVal); + clipboard.setBiome(min.add(x, z), type); + biomeIndex++; + } + } + + private void readEntities(BlockArrayClipboard clipboard, Map schematic) throws IOException { + List entList = requireTag(schematic, "Entities", ListTag.class).getValue(); + if (entList.isEmpty()) { + return; + } + for (Tag et : entList) { + if (!(et instanceof CompoundTag)) { + continue; + } + CompoundTag entityTag = (CompoundTag) et; + Map tags = entityTag.getValue(); + String id = requireTag(tags, "Id", StringTag.class).getValue(); + + EntityType entityType = EntityTypes.get(id); + if (entityType != null) { + Location location = NBTConversions.toLocation(clipboard, + requireTag(tags, "Pos", ListTag.class), + requireTag(tags, "Rotation", ListTag.class)); + BaseEntity state = new BaseEntity(entityType, + entityTag.createBuilder().putString("id", id).remove("Id").build()); + clipboard.createEntity(location, state); + } else { + log.warn("Unknown entity when pasting schematic: " + id); + } + } + } + @Override public void close() throws IOException { inputStream.close(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicWriter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicWriter.java index 8237106f6..f9d9248a6 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicWriter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicWriter.java @@ -21,8 +21,12 @@ package com.sk89q.worldedit.extent.clipboard.io; import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.sk89q.jnbt.ByteArrayTag; import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.DoubleTag; +import com.sk89q.jnbt.FloatTag; import com.sk89q.jnbt.IntArrayTag; import com.sk89q.jnbt.IntTag; import com.sk89q.jnbt.ListTag; @@ -30,9 +34,16 @@ import com.sk89q.jnbt.NBTOutputStream; import com.sk89q.jnbt.ShortTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import java.io.ByteArrayOutputStream; @@ -41,12 +52,15 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * Writes schematic files using the Sponge schematic format. */ public class SpongeSchematicWriter implements ClipboardWriter { + private static final int CURRENT_VERSION = 2; + private static final int MAX_SIZE = Short.MAX_VALUE - Short.MIN_VALUE; private final NBTOutputStream outputStream; @@ -63,17 +77,17 @@ public class SpongeSchematicWriter implements ClipboardWriter { @Override public void write(Clipboard clipboard) throws IOException { // For now always write the latest version. Maybe provide support for earlier if more appear. - outputStream.writeNamedTag("Schematic", new CompoundTag(write1(clipboard))); + outputStream.writeNamedTag("Schematic", new CompoundTag(write2(clipboard))); } /** - * Writes a version 1 schematic file. + * Writes a version 2 schematic file. * * @param clipboard The clipboard * @return The schematic map * @throws IOException If an error occurs */ - private Map write1(Clipboard clipboard) throws IOException { + private Map write2(Clipboard clipboard) throws IOException { Region region = clipboard.getRegion(); BlockVector3 origin = clipboard.getOrigin(); BlockVector3 min = region.getMinimumPoint(); @@ -93,7 +107,9 @@ public class SpongeSchematicWriter implements ClipboardWriter { } Map schematic = new HashMap<>(); - schematic.put("Version", new IntTag(1)); + schematic.put("Version", new IntTag(CURRENT_VERSION)); + schematic.put("DataVersion", new IntTag( + WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataVersion())); Map metadata = new HashMap<>(); metadata.put("WEOffsetX", new IntTag(offset.getBlockX())); @@ -129,10 +145,7 @@ public class SpongeSchematicWriter implements ClipboardWriter { BlockVector3 point = BlockVector3.at(x0, y0, z0); BaseBlock block = clipboard.getFullBlock(point); if (block.getNbtData() != null) { - Map values = new HashMap<>(); - for (Map.Entry entry : block.getNbtData().getValue().entrySet()) { - values.put(entry.getKey(), entry.getValue()); - } + Map values = new HashMap<>(block.getNbtData().getValue()); values.remove("id"); // Remove 'id' if it exists. We want 'Id' @@ -179,9 +192,101 @@ public class SpongeSchematicWriter implements ClipboardWriter { schematic.put("BlockData", new ByteArrayTag(buffer.toByteArray())); schematic.put("TileEntities", new ListTag(CompoundTag.class, tileEntities)); + // version 2 stuff + if (clipboard.hasBiomes()) { + writeBiomes(clipboard, schematic); + } + + if (!clipboard.getEntities().isEmpty()) { + writeEntities(clipboard, schematic); + } + return schematic; } + private void writeBiomes(Clipboard clipboard, Map schematic) { + BlockVector3 min = clipboard.getMinimumPoint(); + int width = clipboard.getRegion().getWidth(); + int length = clipboard.getRegion().getLength(); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(width * length); + + int paletteMax = 0; + Map palette = new HashMap<>(); + + for (int z = 0; z < length; z++) { + int z0 = min.getBlockZ() + z; + for (int x = 0; x < width; x++) { + int x0 = min.getBlockX() + x; + BlockVector2 pt = BlockVector2.at(x0, z0); + BiomeType biome = clipboard.getBiome(pt); + + String biomeKey = biome.getId(); + int biomeId; + if (palette.containsKey(biomeKey)) { + biomeId = palette.get(biomeKey); + } else { + biomeId = paletteMax; + palette.put(biomeKey, biomeId); + paletteMax++; + } + + while ((biomeId & -128) != 0) { + buffer.write(biomeId & 127 | 128); + biomeId >>>= 7; + } + buffer.write(biomeId); + } + } + + schematic.put("BiomePaletteMax", new IntTag(paletteMax)); + + Map paletteTag = new HashMap<>(); + palette.forEach((key, value) -> paletteTag.put(key, new IntTag(value))); + + schematic.put("BiomePalette", new CompoundTag(paletteTag)); + schematic.put("BiomeData", new ByteArrayTag(buffer.toByteArray())); + } + + private void writeEntities(Clipboard clipboard, Map schematic) { + List entities = clipboard.getEntities().stream().map(e -> { + BaseEntity state = e.getState(); + if (state == null) { + return null; + } + Map values = Maps.newHashMap(); + CompoundTag rawData = state.getNbtData(); + if (rawData != null) { + values.putAll(rawData.getValue()); + } + values.remove("id"); + values.put("Id", new StringTag(state.getType().getId())); + values.put("Pos", writeVector(e.getLocation().toVector())); + values.put("Rotation", writeRotation(e.getLocation())); + + return new CompoundTag(values); + }).filter(e -> e != null).collect(Collectors.toList()); + if (entities.isEmpty()) { + return; + } + schematic.put("Entities", new ListTag(CompoundTag.class, entities)); + } + + private Tag writeVector(Vector3 vector) { + List list = new ArrayList(); + list.add(new DoubleTag(vector.getX())); + list.add(new DoubleTag(vector.getY())); + list.add(new DoubleTag(vector.getZ())); + return new ListTag(DoubleTag.class, list); + } + + private Tag writeRotation(Location location) { + List list = new ArrayList(); + list.add(new FloatTag(location.getYaw())); + list.add(new FloatTag(location.getPitch())); + return new ListTag(FloatTag.class, list); + } + @Override public void close() throws IOException { outputStream.close(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/EntityNBTCompatibilityHandler.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/EntityNBTCompatibilityHandler.java new file mode 100644 index 000000000..ffba0b566 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/EntityNBTCompatibilityHandler.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.extent.clipboard.io.legacycompat; + +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.entity.EntityType; + +import java.util.Map; + +public interface EntityNBTCompatibilityHandler { + boolean isAffectedEntity(EntityType type, CompoundTag entityTag); + CompoundTag updateNBT(EntityType type, CompoundTag entityTag); +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/FlowerPotCompatibilityHandler.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/FlowerPotCompatibilityHandler.java new file mode 100644 index 000000000..225a9555e --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/FlowerPotCompatibilityHandler.java @@ -0,0 +1,93 @@ +/* + * 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.clipboard.io.legacycompat; + +import com.sk89q.jnbt.IntTag; +import com.sk89q.jnbt.StringTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.registry.LegacyMapper; + +import java.util.Map; + +public class FlowerPotCompatibilityHandler implements NBTCompatibilityHandler { + @Override + public > boolean isAffectedBlock(B block) { + return block.getBlockType() == BlockTypes.FLOWER_POT; + } + + @Override + public > B updateNBT(B block, Map values) { + Tag item = values.get("Item"); + if (item instanceof StringTag) { + String id = ((StringTag) item).getValue(); + int data = 0; + Tag dataTag = values.get("Data"); + if (dataTag instanceof IntTag) { + data = ((IntTag) dataTag).getValue(); + } + BlockState newState = convertLegacyBlockType(id, data); + if (newState != null) { + return (B) newState; // generics pls :\ + } + } + return block; + } + + private BlockState convertLegacyBlockType(String id, int data) { + int newId = 0; + switch (id) { + case "minecraft:red_flower": + newId = 38; // now poppy + break; + case "minecraft:yellow_flower": + newId = 37; // now dandelion + break; + case "minecraft:sapling": + newId = 6; // oak_sapling + break; + case "minecraft:deadbush": + case "minecraft:tallgrass": + newId = 31; // dead_bush with fern and grass (not 32!) + break; + default: + break; + } + String plantedName = null; + if (newId == 0) { + plantedName = id.substring(10); + } else { + BlockState plantedWithData = LegacyMapper.getInstance().getBlockFromLegacy(newId, data); + if (plantedWithData != null) { + plantedName = plantedWithData.getBlockType().getId().substring(10); // remove "minecraft:" + } + } + if (plantedName != null) { + BlockType potAndPlanted = BlockTypes.get("minecraft:potted_" + plantedName); + if (potAndPlanted != null) { + return potAndPlanted.getDefaultState(); + } + } + return null; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/NBTCompatibilityHandler.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/NBTCompatibilityHandler.java index 88c344566..0f631f561 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/NBTCompatibilityHandler.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/NBTCompatibilityHandler.java @@ -26,5 +26,5 @@ import java.util.Map; public interface NBTCompatibilityHandler { > boolean isAffectedBlock(B block); - > void updateNBT(B block, Map values); + > B updateNBT(B block, Map values); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/NoteBlockCompatibilityHandler.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/NoteBlockCompatibilityHandler.java new file mode 100644 index 000000000..940086b8f --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/NoteBlockCompatibilityHandler.java @@ -0,0 +1,62 @@ +/* + * 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.clipboard.io.legacycompat; + +import com.sk89q.jnbt.ByteTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.registry.state.IntegerProperty; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockTypes; + +import java.util.Map; + +public class NoteBlockCompatibilityHandler implements NBTCompatibilityHandler { + private static final IntegerProperty NoteProperty; + + static { + IntegerProperty temp; + try { + temp = (IntegerProperty) (Property) BlockTypes.NOTE_BLOCK.getProperty("note"); + } catch (NullPointerException | IllegalArgumentException | ClassCastException e) { + temp = null; + } + NoteProperty = temp; + } + + @Override + public > boolean isAffectedBlock(B block) { + return NoteProperty != null && block.getBlockType() == BlockTypes.NOTE_BLOCK; + } + + @Override + public > B updateNBT(B block, Map values) { + // note that instrument was not stored (in state or nbt) previously. + // it will be updated to the block below when it gets set into the world for the first time + Tag noteTag = values.get("note"); + if (noteTag instanceof ByteTag) { + Byte note = ((ByteTag) noteTag).getValue(); + if (note != null) { + return block.with(NoteProperty, (int) note); + } + } + return block; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/Pre13HangingCompatibilityHandler.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/Pre13HangingCompatibilityHandler.java new file mode 100644 index 000000000..62f40d79c --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/Pre13HangingCompatibilityHandler.java @@ -0,0 +1,62 @@ +/* + * 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.clipboard.io.legacycompat; + +import com.sk89q.jnbt.ByteTag; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.jnbt.CompoundTagBuilder; +import com.sk89q.worldedit.internal.helper.MCDirections; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.world.entity.EntityType; + +public class Pre13HangingCompatibilityHandler implements EntityNBTCompatibilityHandler { + + @Override + public boolean isAffectedEntity(EntityType type, CompoundTag tag) { + if (!type.getId().startsWith("minecraft:")) { + return false; + } + boolean hasLegacyDirection = tag.containsKey("Dir") || tag.containsKey("Direction"); + boolean hasFacing = tag.containsKey("Facing"); + return hasLegacyDirection || hasFacing; + } + + @Override + public CompoundTag updateNBT(EntityType type, CompoundTag tag) { + boolean hasLegacyDir = tag.containsKey("Dir"); + boolean hasLegacyDirection = tag.containsKey("Direction"); + boolean hasPre113Facing = tag.containsKey("Facing"); + Direction newDirection; + if (hasLegacyDir) { + newDirection = MCDirections.fromPre13Hanging(MCDirections.fromLegacyHanging((byte) tag.asInt("Dir"))); + } else if (hasLegacyDirection) { + newDirection = MCDirections.fromPre13Hanging(tag.asInt("Direction")); + } else if (hasPre113Facing) { + newDirection = MCDirections.fromPre13Hanging(tag.asInt("Facing")); + } else { + return tag; + } + byte hangingByte = (byte) MCDirections.toHanging(newDirection); + CompoundTagBuilder builder = tag.createBuilder(); + builder.putByte("Facing", hangingByte); + return builder.build(); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/SignCompatibilityHandler.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/SignCompatibilityHandler.java index ad75ed911..ff1ebd53f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/SignCompatibilityHandler.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/SignCompatibilityHandler.java @@ -39,7 +39,7 @@ public class SignCompatibilityHandler implements NBTCompatibilityHandler { } @Override - public > void updateNBT(B block, Map values) { + public > B updateNBT(B block, Map values) { for (int i = 0; i < 4; ++i) { String key = "Text" + (i + 1); Tag value = values.get(key); @@ -69,5 +69,6 @@ public class SignCompatibilityHandler implements NBTCompatibilityHandler { values.put("Text" + (i + 1), new StringTag(jsonTextObject.toString())); } } + return block; } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/SkullBlockCompatibilityHandler.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/SkullBlockCompatibilityHandler.java new file mode 100644 index 000000000..f4fe1a3ce --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/SkullBlockCompatibilityHandler.java @@ -0,0 +1,100 @@ +/* + * 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.clipboard.io.legacycompat; + +import com.sk89q.jnbt.ByteTag; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.registry.state.DirectionalProperty; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; + +import java.util.Map; + +public class SkullBlockCompatibilityHandler implements NBTCompatibilityHandler { + + private static final DirectionalProperty FacingProperty; + + static { + DirectionalProperty tempFacing; + try { + tempFacing = (DirectionalProperty) (Property) BlockTypes.SKELETON_WALL_SKULL.getProperty("facing"); + } catch (NullPointerException | IllegalArgumentException | ClassCastException e) { + tempFacing = null; + } + FacingProperty = tempFacing; + } + + @Override + public > boolean isAffectedBlock(B block) { + return block.getBlockType() == BlockTypes.SKELETON_SKULL + || block.getBlockType() == BlockTypes.SKELETON_WALL_SKULL; + } + + @Override + public > B updateNBT(B block, Map values) { + boolean isWall = block.getBlockType() == BlockTypes.SKELETON_WALL_SKULL; + Tag typeTag = values.get("SkullType"); + if (typeTag instanceof ByteTag) { + String skullType = convertSkullType(((ByteTag) typeTag).getValue(), isWall); + if (skullType != null) { + BlockType type = BlockTypes.get("minecraft:" + skullType); + if (type != null) { + BlockState state = type.getDefaultState(); + if (isWall) { + Property newProp = type.getProperty("facing"); + state = state.with(newProp, block.getState(FacingProperty)); + } else { + Tag rotTag = values.get("Rot"); + if (rotTag instanceof ByteTag) { + Property newProp = type.getProperty("rotation"); + state = state.with(newProp, (int) ((ByteTag) rotTag).getValue()); + } + } + values.remove("SkullType"); + values.remove("Rot"); + return (B) state; + } + } + } + return block; + } + + private String convertSkullType(Byte oldType, boolean isWall) { + switch (oldType) { + case 0: + return isWall ? "skeleton_wall_skull" : "skeleton_skull"; + case 1: + return isWall ? "wither_skeleton_wall_skull" : "wither_skeleton_skull"; + case 2: + return isWall ? "zombie_wall_head" : "zombie_head"; + case 3: + return isWall ? "player_wall_head" : "player_head"; + case 4: + return isWall ? "creeper_wall_head" : "creeper_head"; + case 5: + return isWall ? "dragon_wall_head" : "dragon_head"; + default: + return null; + } + } +} 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..fa1ee8b34 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/biome/ExtentBiomeCopy.java @@ -0,0 +1,72 @@ +/* + * 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.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/entity/ExtentEntityCopy.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/entity/ExtentEntityCopy.java index 7205c1212..3c882a18a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/entity/ExtentEntityCopy.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/entity/ExtentEntityCopy.java @@ -130,8 +130,6 @@ public class ExtentEntityCopy implements EntityFunction { if (tag != null) { // Handle hanging entities (paintings, item frames, etc.) boolean hasTilePosition = tag.containsKey("TileX") && tag.containsKey("TileY") && tag.containsKey("TileZ"); - boolean hasDirection = tag.containsKey("Direction"); - boolean hasLegacyDirection = tag.containsKey("Dir"); boolean hasFacing = tag.containsKey("Facing"); if (hasTilePosition) { @@ -143,27 +141,15 @@ public class ExtentEntityCopy implements EntityFunction { .putInt("TileY", newTilePosition.getBlockY()) .putInt("TileZ", newTilePosition.getBlockZ()); - if (hasDirection || hasLegacyDirection || hasFacing) { - int d; - if (hasDirection) { - d = tag.asInt("Direction"); - } else if (hasLegacyDirection) { - d = MCDirections.fromLegacyHanging((byte) tag.asInt("Dir")); - } else { - d = tag.asInt("Facing"); - } - - Direction direction = MCDirections.fromHanging(d); + if (hasFacing) { + Direction direction = MCDirections.fromHanging(tag.asInt("Facing")); if (direction != null) { Vector3 vector = transform.apply(direction.toVector()).subtract(transform.apply(Vector3.ZERO)).normalize(); Direction newDirection = Direction.findClosest(vector, Flag.CARDINAL); if (newDirection != null) { - byte hangingByte = (byte) MCDirections.toHanging(newDirection); - builder.putByte("Direction", hangingByte); - builder.putByte("Facing", hangingByte); - builder.putByte("Dir", MCDirections.toLegacyHanging(MCDirections.toHanging(newDirection))); + builder.putByte("Facing", (byte) MCDirections.toHanging(newDirection)); } } } 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..9512b5c3d 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,23 @@ 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.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,11 +67,18 @@ 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; + private RegionVisitor lastVisitor; - private int affected; + private FlatRegionVisitor lastBiomeVisitor; + private EntityVisitor lastEntityVisitor; + + private int affectedBlocks; + private int affectedBiomeCols; + private int affectedEntities; /** * Create a new copy using the region's lowest minimum point as the @@ -222,21 +235,50 @@ 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. * * @return the number of affected */ public int getAffected() { - return affected; + return affectedBlocks + affectedBiomeCols + affectedEntities; } @Override public Operation resume(RunContext run) throws WorldEditException { if (lastVisitor != null) { - affected += lastVisitor.getAffected(); + affectedBlocks += lastVisitor.getAffected(); lastVisitor = null; } + if (lastBiomeVisitor != null) { + affectedBiomeCols += lastBiomeVisitor.getAffected(); + lastBiomeVisitor = null; + } + if (lastEntityVisitor != null) { + affectedEntities += lastEntityVisitor.getAffected(); + lastEntityVisitor = null; + } if (repetitions > 0) { repetitions--; @@ -254,6 +296,23 @@ 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(); + FlatRegionFunction biomeFunction = biomeMask == null ? biomeCopy + : new FlatRegionMaskingFilter(biomeMask, biomeCopy); + FlatRegionVisitor biomeVisitor = new FlatRegionVisitor(((FlatRegion) region), biomeFunction); + ops.add(biomeVisitor); + lastBiomeVisitor = biomeVisitor; + } + if (copyingEntities) { ExtentEntityCopy entityCopy = new ExtentEntityCopy(from.toVector3(), destination, to.toVector3(), currentTransform); entityCopy.setRemoving(removingEntities); @@ -263,10 +322,11 @@ 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); + lastEntityVisitor = entityVisitor; } + + return new DelegateOperation(this, new OperationQueue(ops)); } else { return null; } @@ -278,6 +338,24 @@ public class ForwardExtentCopy implements Operation { @Override public void addStatusMessages(List messages) { + StringBuilder msg = new StringBuilder(); + msg.append(affectedBlocks).append(" block(s)"); + if (affectedBiomeCols > 0) { + if (affectedEntities > 0) { + msg.append(", "); + } else { + msg.append(" and "); + } + msg.append(affectedBiomeCols).append(" biome(s)"); + } + if (affectedEntities > 0) { + if (affectedBiomeCols > 0) { + msg.append(","); + } + msg.append(" and ").append(affectedEntities).append(" entities(s)"); + } + msg.append(" affected."); + messages.add(msg.toString()); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/helper/MCDirections.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/helper/MCDirections.java index 16e25e354..959e30ae4 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/helper/MCDirections.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/helper/MCDirections.java @@ -30,6 +30,44 @@ public final class MCDirections { } public static Direction fromHanging(int i) { + switch (i) { + case 0: + return Direction.DOWN; + case 1: + return Direction.UP; + case 2: + return Direction.NORTH; + case 3: + return Direction.SOUTH; + case 4: + return Direction.WEST; + case 5: + return Direction.EAST; + default: + return Direction.DOWN; + } + } + + public static int toHanging(Direction direction) { + switch (direction) { + case DOWN: + return 0; + case UP: + return 1; + case NORTH: + return 2; + case SOUTH: + return 3; + case WEST: + return 4; + case EAST: + return 5; + default: + return 0; + } + } + + public static Direction fromPre13Hanging(int i) { switch (i) { case 0: return Direction.SOUTH; @@ -44,21 +82,6 @@ public final class MCDirections { } } - public static int toHanging(Direction direction) { - switch (direction) { - case SOUTH: - return 0; - case WEST: - return 1; - case NORTH: - return 2; - case EAST: - return 3; - default: - return 0; - } - } - public static int fromLegacyHanging(byte i) { switch (i) { case 0: return 2; @@ -68,15 +91,6 @@ public final class MCDirections { } } - public static byte toLegacyHanging(int i) { - switch (i) { - case 0: return (byte) 2; - case 1: return (byte) 1; - case 2: return (byte) 0; - default: return (byte) 3; - } - } - public static Direction fromRotation(int i) { switch (i) { case 0: 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..30966535f 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,23 @@ 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) { + if (sourceMask == null) { + this.sourceMask = Masks.alwaysTrue(); + return this; + } + this.sourceMask = sourceMask; + return this; + } + /** * Set whether air blocks in the source are skipped over when pasting. * @@ -77,6 +101,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 +134,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; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/NBTConversions.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/NBTConversions.java index 6b61bddf9..3da5c0047 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/NBTConversions.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/storage/NBTConversions.java @@ -52,7 +52,7 @@ public final class NBTConversions { return new Location( extent, positionTag.asDouble(0), positionTag.asDouble(1), positionTag.asDouble(2), - (float) directionTag.asDouble(0), (float) directionTag.asDouble(1)); + directionTag.getFloat(0), directionTag.getFloat(1)); } } diff --git a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlatform.java b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlatform.java index 5989634bd..7f4fa238a 100644 --- a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlatform.java +++ b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlatform.java @@ -69,6 +69,12 @@ class ForgePlatform extends AbstractPlatform implements MultiUserPlatform { return ForgeRegistries.getInstance(); } + @Override + public int getDataVersion() { + // TODO switch to SharedConstants in 1.14 + return 1631; + } + @Override public boolean isValidMobType(String type) { return net.minecraftforge.registries.ForgeRegistries.ENTITIES.containsKey(new ResourceLocation(type)); diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlatform.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlatform.java index 29d2fad5a..07cb23fba 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlatform.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlatform.java @@ -73,6 +73,12 @@ class SpongePlatform extends AbstractPlatform implements MultiUserPlatform { return SpongeRegistries.getInstance(); } + @Override + public int getDataVersion() { + // TODO add to adapter - org.spongepowered.common.data.util.DataUtil#MINECRAFT_DATA_VERSION + return 1631; + } + @Override public boolean isValidMobType(String type) { return Sponge.getRegistry().getType(EntityType.class, type).isPresent();