From d6ad027fc8746a59af3d185393466007e11b9ad8 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Sun, 17 Apr 2016 05:15:21 +1000 Subject: [PATCH] Fix block rotation corrupting block cache --- core/src/main/java/com/boydti/fawe/Fawe.java | 4 + .../main/java/com/boydti/fawe/FaweCache.java | 30 +- .../com/sk89q/worldedit/CuboidClipboard.java | 922 ++++++++++++++++++ .../command/FlattenedClipboardTransform.java | 141 --- .../extent/clipboard/BlockArrayClipboard.java | 16 +- .../transform/BlockTransformExtent.java | 186 ++++ 6 files changed, 1148 insertions(+), 151 deletions(-) create mode 100644 core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java delete mode 100644 core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java create mode 100644 core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java index 28c6ea95..6e1de1b8 100644 --- a/core/src/main/java/com/boydti/fawe/Fawe.java +++ b/core/src/main/java/com/boydti/fawe/Fawe.java @@ -14,6 +14,7 @@ import com.boydti.fawe.util.MemUtil; import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.WEManager; import com.boydti.fawe.util.WESubscriber; +import com.sk89q.worldedit.CuboidClipboard; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.Vector; @@ -22,6 +23,7 @@ import com.sk89q.worldedit.command.SchematicCommands; import com.sk89q.worldedit.command.ScriptingCommands; import com.sk89q.worldedit.extension.platform.CommandManager; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.extent.transform.BlockTransformExtent; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.visitor.BreadthFirstSearch; import com.sk89q.worldedit.function.visitor.DownwardVisitor; @@ -229,6 +231,8 @@ public class Fawe { LocalSession.inject(); BlockArrayClipboard.inject(); CuboidRegion.inject(); + CuboidClipboard.inject(); + BlockTransformExtent.inject(); Vector.inject(); try { CommandManager.inject(); diff --git a/core/src/main/java/com/boydti/fawe/FaweCache.java b/core/src/main/java/com/boydti/fawe/FaweCache.java index 569b3272..939a35f8 100644 --- a/core/src/main/java/com/boydti/fawe/FaweCache.java +++ b/core/src/main/java/com/boydti/fawe/FaweCache.java @@ -1,6 +1,7 @@ package com.boydti.fawe; import com.boydti.fawe.object.PseudoRandom; +import com.sk89q.worldedit.CuboidClipboard; import com.sk89q.worldedit.blocks.BaseBlock; public class FaweCache { @@ -43,7 +44,34 @@ public class FaweCache { for (int i = 0; i < Short.MAX_VALUE; i++) { int id = i >> 4; int data = i & 0xf; - CACHE_BLOCK[i] = new BaseBlock(id, data); + CACHE_BLOCK[i] = new BaseBlock(id, data) { + @Override + public void setData(int data) { + throw new IllegalStateException("Cannot set data"); + } + + @Override + public void setId(int id) { + throw new IllegalStateException("Cannot set id"); + } + + @Override + public BaseBlock flip() { + BaseBlock clone = new BaseBlock(getId(), getData(), getNbtData()); + return clone.flip(); + } + + @Override + public BaseBlock flip(CuboidClipboard.FlipDirection direction) { + BaseBlock clone = new BaseBlock(getId(), getData(), getNbtData()); + return clone.flip(direction); + } + + @Override + public boolean hasWildcardData() { + return true; + } + }; } } diff --git a/core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java b/core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java new file mode 100644 index 00000000..56b87e75 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/CuboidClipboard.java @@ -0,0 +1,922 @@ +/* + * 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; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.object.IntegerTrio; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BlockID; +import com.sk89q.worldedit.command.ClipboardCommands; +import com.sk89q.worldedit.command.SchematicCommands; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.schematic.SchematicFormat; +import com.sk89q.worldedit.util.Countable; +import com.sk89q.worldedit.world.DataException; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * The clipboard remembers the state of a cuboid region. + * + * @deprecated This is slowly being replaced with {@link Clipboard}, which is + * far more versatile. Transforms are supported using affine + * transformations and full entity support is provided because + * the clipboard properly implements {@link Extent}. However, + * the new clipboard class is only available in WorldEdit 6.x and + * beyond. We intend on keeping this deprecated class in WorldEdit + * for an extended amount of time so there is no rush to + * switch (but new features will not be supported). To copy between + * a clipboard and a world (or between any two {@code Extent}s), + * one can use {@link ForwardExtentCopy}. See + * {@link ClipboardCommands} and {@link SchematicCommands} for + * more information. + */ +@Deprecated +public class CuboidClipboard { + + /** + * An enum of possible flip directions. + */ + public enum FlipDirection { + NORTH_SOUTH, + WEST_EAST, + UP_DOWN + } + + public byte[][] ids; + public byte[][] datas; + public HashMap nbtMap; + public List entities = new ArrayList(); + + public Vector size; + private int dx; + private int dxz; + private Vector offset; + private Vector origin; + + /** + * Constructs the clipboard. + * + * @param size the dimensions of the clipboard (should be at least 1 on every dimension) + */ + public CuboidClipboard(Vector size) { + checkNotNull(size); + origin = new Vector(); + offset = new Vector(); + this.size = size; + this.dx = size.getBlockX(); + this.dxz = dx * size.getBlockZ(); + ids = new byte[dx * size.getBlockZ() * ((size.getBlockY() + 15) >> 4)][]; + nbtMap = new HashMap<>(); + } + + /** + * Constructs the clipboard. + * + * @param size the dimensions of the clipboard (should be at least 1 on every dimension) + * @param origin the origin point where the copy was made, which must be the + * {@link CuboidRegion#getMinimumPoint()} relative to the copy + */ + public CuboidClipboard(Vector size, Vector origin) { + checkNotNull(size); + checkNotNull(origin); + this.origin = origin; + this.offset = new Vector(); + this.size = size; + this.dx = size.getBlockX(); + this.dxz = dx * size.getBlockZ(); + ids = new byte[dx * size.getBlockZ() * ((size.getBlockY() + 15) >> 4)][]; + nbtMap = new HashMap<>(); + } + + /** + * Constructs the clipboard. + * + * @param size the dimensions of the clipboard (should be at least 1 on every dimension) + * @param origin the origin point where the copy was made, which must be the + * {@link CuboidRegion#getMinimumPoint()} relative to the copy + * @param offset the offset from the minimum point of the copy where the user was + */ + public CuboidClipboard(Vector size, Vector origin, Vector offset) { + checkNotNull(size); + checkNotNull(origin); + checkNotNull(offset); + this.origin = origin; + this.offset = offset; + this.size = size; + this.dx = size.getBlockX(); + this.dxz = dx * size.getBlockZ(); + ids = new byte[dx * size.getBlockZ() * ((size.getBlockY() + 15) >> 4)][]; + nbtMap = new HashMap<>(); + } + + public BaseBlock getBlock(Vector position) { + return getBlock(position.getBlockX(), position.getBlockY(), position.getBlockZ()); + } + + public BaseBlock getBlock(int x, int y, int z) { + int i = x + z * dx + (y >> 4) * dxz; + byte[] idArray = ids[i]; + if (idArray == null) { + return FaweCache.CACHE_BLOCK[0]; + } + int y2 = y & 0xF; + int id = idArray[y2] & 0xFF; + BaseBlock block; + if (!FaweCache.hasData(id) || datas == null) { + block = FaweCache.CACHE_BLOCK[id << 4]; + } else { + byte[] dataArray = datas[i]; + if (dataArray == null) { + block = FaweCache.CACHE_BLOCK[id << 4]; + } else { + block = FaweCache.CACHE_BLOCK[(id << 4) + dataArray[y2]]; + } + } + if (FaweCache.hasNBT(id)) { + CompoundTag nbt = nbtMap.get(new IntegerTrio(x, y, z)); + if (nbt != null) { + block = new BaseBlock(block.getId(), block.getData()); + block.setNbtData(nbt); + } + } + return block; + } + + public BaseBlock getLazyBlock(Vector position) { + return getBlock(position); + } + + public boolean setBlock(Vector location, BaseBlock block) { + return setBlock(location.getBlockX(),location.getBlockY(),location.getBlockZ(), block); + } + + public boolean setBlock(int x, int y, int z, BaseBlock block) { + if (block == null) { + int i = x + z * dx + (y >> 4) * dxz; + int y2 = y & 0xF; + byte[] idArray = ids[i]; + if (idArray == null) { + return true; + } + idArray[y2] = (byte) 0; + if (datas != null) { + byte[] dataArray = datas[i]; + if (dataArray != null) { + dataArray[y2] = (byte) 0; + } + } + nbtMap.remove(new IntegerTrio(x, y, z)); + return true; + } + final int id = block.getId(); + switch (id) { + case 0: + return true; + case 54: + case 130: + case 142: + case 27: + case 137: + case 52: + case 154: + case 84: + case 25: + case 144: + case 138: + case 176: + case 177: + case 63: + case 119: + case 68: + case 323: + case 117: + case 116: + case 28: + case 66: + case 157: + case 61: + case 62: + case 140: + case 146: + case 149: + case 150: + case 158: + case 23: + case 123: + case 124: + case 29: + case 33: + case 151: + case 178: { + if (block.hasNbtData()) { + nbtMap.put(new IntegerTrio(x, y, z), block.getNbtData()); + } + int i = x + z * dx + (y >> 4) * dxz; + int y2 = y & 0xF; + byte[] idArray = ids[i]; + if (idArray == null) { + idArray = new byte[16]; + ids[i] = idArray; + } + idArray[y2] = (byte) id; + if (FaweCache.hasData(id)) { + int data = block.getData(); + if (data == 0) { + return true; + } + if (datas == null) { + datas = new byte[dx * size.getBlockZ() * ((size.getBlockY() + 15) >> 4)][]; + } + byte[] dataArray = datas[i]; + if (dataArray == null) { + dataArray = datas[i] = new byte[16]; + } + dataArray[y2] = (byte) data; + } + return true; + } + case 2: + case 4: + case 13: + case 14: + case 15: + case 20: + case 21: + case 22: + case 30: + case 32: + case 37: + case 39: + case 40: + case 41: + case 42: + case 45: + case 46: + case 47: + case 48: + case 49: + case 51: + case 56: + case 57: + case 58: + case 60: + case 7: + case 8: + case 9: + case 10: + case 11: + case 73: + case 74: + case 78: + case 79: + case 80: + case 81: + case 82: + case 83: + case 85: + case 87: + case 88: + case 101: + case 102: + case 103: + case 110: + case 112: + case 113: + case 121: + case 122: + case 129: + case 133: + case 165: + case 166: + case 169: + case 170: + case 172: + case 173: + case 174: + case 181: + case 182: + case 188: + case 189: + case 190: + case 191: + case 192: { + int i = x + z * dx + (y >> 4) * dxz; + int y2 = y & 0xF; + byte[] idArray = ids[i]; + if (idArray == null) { + idArray = new byte[16]; + ids[i] = idArray; + } + idArray[y2] = (byte) id; + return true; + } + default: { + int i = x + z * dx + (y >> 4) * dxz; + int y2 = y & 0xF; + byte[] idArray = ids[i]; + if (idArray == null) { + idArray = new byte[16]; + ids[i] = idArray; + } + idArray[y2] = (byte) id; + int data = block.getData(); + if (data == 0) { + return true; + } + if (datas == null) { + datas = new byte[dx * size.getBlockZ() * ((size.getBlockY() + 15) >> 4)][]; + } + byte[] dataArray = datas[i]; + if (dataArray == null) { + dataArray = datas[i] = new byte[16]; + } + dataArray[y2] = (byte) data; + return true; + } + } + } + + /** + * Get the width (X-direction) of the clipboard. + * + * @return width + */ + public int getWidth() { + return size.getBlockX(); + } + + /** + * Get the length (Z-direction) of the clipboard. + * + * @return length + */ + public int getLength() { + return size.getBlockZ(); + } + + /** + * Get the height (Y-direction) of the clipboard. + * + * @return height + */ + public int getHeight() { + return size.getBlockY(); + } + + /** + * Rotate the clipboard in 2D. It can only rotate by angles divisible by 90. + * + * @param angle in degrees + */ + @SuppressWarnings("deprecation") + public void rotate2D(int angle) { + angle = angle % 360; + if (angle % 90 != 0) { // Can only rotate 90 degrees at the moment + return; + } + if (angle == 0) { + return; + } + if (angle > 180) { + angle -= 360; + } + final boolean reverse = angle < 0; + final int numRotations = Math.abs((int) Math.floor(angle / 90.0)); + + final int width = getWidth(); + final int length = getLength(); + final int height = getHeight(); + final Vector sizeRotated = size.transform2D(angle, 0, 0, 0, 0); + final int shiftX = sizeRotated.getX() < 0 ? -sizeRotated.getBlockX() - 1 : 0; + final int shiftZ = sizeRotated.getZ() < 0 ? -sizeRotated.getBlockZ() - 1 : 0; + + CuboidClipboard cloned = new CuboidClipboard(sizeRotated); + + BaseBlock air = FaweCache.CACHE_BLOCK[0]; + + for (int x = 0; x < width; ++x) { + for (int z = 0; z < length; ++z) { + final Vector2D v = new Vector2D(x, z).transform2D(angle, 0, 0, shiftX, shiftZ); + final int newX = v.getBlockX(); + final int newZ = v.getBlockZ(); + for (int y = 0; y < height; ++y) { + BaseBlock block = getBlock(x, y, z); + if (block == air || block == null) { + continue; + } + if (reverse) { + for (int i = 0; i < numRotations; ++i) { + block.rotate90Reverse(); + } + } else { + for (int i = 0; i < numRotations; ++i) { + block.rotate90(); + } + } + cloned.setBlock(newX,y,newZ, block); + } + } + } + this.ids = cloned.ids; + this.datas = cloned.datas; + this.nbtMap = cloned.nbtMap; + size = new Vector(Math.abs(sizeRotated.getBlockX()), + Math.abs(sizeRotated.getBlockY()), + Math.abs(sizeRotated.getBlockZ())); + offset = offset.transform2D(angle, 0, 0, 0, 0) + .subtract(shiftX, 0, shiftZ); + } + + /** + * Flip the clipboard. + * + * @param dir direction to flip + */ + public void flip(FlipDirection dir) { + flip(dir, false); + } + + /** + * Flip the clipboard. + * + * @param dir direction to flip + * @param aroundPlayer flip the offset around the player + */ + @SuppressWarnings("deprecation") + public void flip(FlipDirection dir, boolean aroundPlayer) { + checkNotNull(dir); + + final int width = getWidth(); + final int length = getLength(); + final int height = getHeight(); + + switch (dir) { + case WEST_EAST: + final int wid = (int) Math.ceil(width / 2.0f); + for (int xs = 0; xs < wid; ++xs) { + for (int z = 0; z < length; ++z) { + for (int y = 0; y < height; ++y) { + BaseBlock block1 = getBlock(xs,y,z); + if (block1 != null) { + block1.flip(dir); + } + // Skip the center plane + if (xs == width - xs - 1) { + continue; + } + BaseBlock block2 = getBlock(width - xs - 1,y,z); + if (block2 != null) { + block2.flip(dir); + } + setBlock(xs,y,z, block2); + setBlock(width - xs - 1,y,z, block1); + } + } + } + + if (aroundPlayer) { + offset = offset.setX(1 - offset.getX() - width); + } + + break; + + case NORTH_SOUTH: + final int len = (int) Math.ceil(length / 2.0f); + for (int zs = 0; zs < len; ++zs) { + for (int x = 0; x < width; ++x) { + for (int y = 0; y < height; ++y) { + BaseBlock block1 = getBlock(x,y,zs); + if (block1 != null) { + block1.flip(dir); + } + + // Skip the center plane + if (zs == length - zs - 1) { + continue; + } + + BaseBlock block2 = getBlock(x,y,length - zs - 1); + if (block2 != null) { + block2.flip(dir); + } + + setBlock(x,y,zs,block2); + setBlock(x,y,length - zs - 1,block1); + + + } + } + } + + if (aroundPlayer) { + offset = offset.setZ(1 - offset.getZ() - length); + } + + break; + + case UP_DOWN: + final int hei = (int) Math.ceil(height / 2.0f); + for (int ys = 0; ys < hei; ++ys) { + for (int x = 0; x < width; ++x) { + for (int z = 0; z < length; ++z) { + BaseBlock block1 = getBlock(x,ys,z); + if (block1 != null) { + block1.flip(dir); + } + + // Skip the center plane + if (ys == height - ys - 1) { + continue; + } + + BaseBlock block2 = getBlock(x,height - ys - 1,z); + if (block2 != null) { + block2.flip(dir); + } + + setBlock(x,ys,z, block2); + setBlock(x,height - ys - 1,z, block1); + } + } + } + + if (aroundPlayer) { + offset = offset.setY(1 - offset.getY() - height); + } + + break; + } + } + + /** + * Copies blocks to the clipboard. + * + * @param editSession the EditSession from which to take the blocks + */ + public void copy(EditSession editSession) { + for (int x = 0; x < size.getBlockX(); ++x) { + for (int y = 0; y < size.getBlockY(); ++y) { + for (int z = 0; z < size.getBlockZ(); ++z) { + setBlock(x, y, z, editSession.getBlock(new Vector(x, y, z).add(getOrigin()))); + } + } + } + } + + /** + * Copies blocks to the clipboard. + * + * @param editSession The EditSession from which to take the blocks + * @param region A region that further constrains which blocks to take. + */ + public void copy(EditSession editSession, Region region) { + for (int x = 0; x < size.getBlockX(); ++x) { + for (int y = 0; y < size.getBlockY(); ++y) { + for (int z = 0; z < size.getBlockZ(); ++z) { + final Vector pt = new Vector(x, y, z).add(getOrigin()); + if (region.contains(pt)) { + setBlock(x, y, z, editSession.getBlock(pt)); + } else { + setBlock(x, y, z, null); + } + } + } + } + } + + /** + * Paste the clipboard at the given location using the given {@code EditSession}. + * + *

This method blocks the server/game until the entire clipboard is + * pasted. In the future, {@link ForwardExtentCopy} will be recommended, + * which, if combined with the proposed operation scheduler framework, + * will not freeze the game/server.

+ * + * @param editSession the EditSession to which blocks are to be copied to + * @param newOrigin the new origin point (must correspond to the minimum point of the cuboid) + * @param noAir true to not copy air blocks in the source + * @throws MaxChangedBlocksException thrown if too many blocks were changed + */ + public void paste(EditSession editSession, Vector newOrigin, boolean noAir) throws MaxChangedBlocksException { + paste(editSession, newOrigin, noAir, false); + } + + /** + * Paste the clipboard at the given location using the given {@code EditSession}. + * + *

This method blocks the server/game until the entire clipboard is + * pasted. In the future, {@link ForwardExtentCopy} will be recommended, + * which, if combined with the proposed operation scheduler framework, + * will not freeze the game/server.

+ * + * @param editSession the EditSession to which blocks are to be copied to + * @param newOrigin the new origin point (must correspond to the minimum point of the cuboid) + * @param noAir true to not copy air blocks in the source + * @param entities true to copy entities + * @throws MaxChangedBlocksException thrown if too many blocks were changed + */ + public void paste(EditSession editSession, Vector newOrigin, boolean noAir, boolean entities) throws MaxChangedBlocksException { + place(editSession, newOrigin.add(offset), noAir); + if (entities) { + pasteEntities(newOrigin.add(offset)); + } + } + + /** + * Paste the clipboard at the given location using the given {@code EditSession}. + * + *

This method blocks the server/game until the entire clipboard is + * pasted. In the future, {@link ForwardExtentCopy} will be recommended, + * which, if combined with the proposed operation scheduler framework, + * will not freeze the game/server.

+ * + * @param editSession the EditSession to which blocks are to be copied to + * @param newOrigin the new origin point (must correspond to the minimum point of the cuboid) + * @param noAir true to not copy air blocks in the source + * @throws MaxChangedBlocksException thrown if too many blocks were changed + */ + public void place(EditSession editSession, Vector newOrigin, boolean noAir) throws MaxChangedBlocksException { + Vector v = new Vector(0,0,0); + int ox = newOrigin.getBlockX(); + int oy = newOrigin.getBlockY(); + int oz = newOrigin.getBlockZ(); + for (int x = 0; x < size.getBlockX(); ++x) { + v.x = x + ox; + for (int y = 0; y < size.getBlockY(); ++y) { + v.y = y + oy; + for (int z = 0; z < size.getBlockZ(); ++z) { + v.z = z + oz; + final BaseBlock block = getBlock(x,y,z); + if (block == null) { + continue; + } + if (noAir && block.isAir()) { + continue; + } + editSession.setBlock(new Vector(x, y, z).add(newOrigin), block); + } + } + } + } + + /** + * Paste the stored entities to the given position. + * + * @param newOrigin the new origin + * @return a list of entities that were pasted + */ + public LocalEntity[] pasteEntities(Vector newOrigin) { + LocalEntity[] entities = new LocalEntity[this.entities.size()]; + for (int i = 0; i < this.entities.size(); ++i) { + CopiedEntity copied = this.entities.get(i); + if (copied.entity.spawn(copied.entity.getPosition().setPosition(copied.relativePosition.add(newOrigin)))) { + entities[i] = copied.entity; + } + } + return entities; + } + + /** + * Store an entity. + * + * @param entity the entity + */ + public void storeEntity(LocalEntity entity) { + this.entities.add(new CopiedEntity(entity)); + } + + /** + * Get the block at the given position. + * + *

If the position is out of bounds, air will be returned.

+ * + * @param position the point, relative to the origin of the copy (0, 0, 0) and not to the actual copy origin + * @return air, if this block was outside the (non-cuboid) selection while copying + * @throws ArrayIndexOutOfBoundsException if the position is outside the bounds of the CuboidClipboard + * @deprecated use {@link #getBlock(Vector)} instead + */ + @Deprecated + public BaseBlock getPoint(Vector position) throws ArrayIndexOutOfBoundsException { + final BaseBlock block = getBlock(position); + if (block == null) { + return new BaseBlock(BlockID.AIR); + } + + return block; + } + + /** + * Get the dimensions of the clipboard. + * + * @return the dimensions, where (1, 1, 1) is 1 wide, 1 across, 1 deep + */ + public Vector getSize() { + return size; + } + + /** + * Saves the clipboard data to a .schematic-format file. + * + * @param path the path to the file to save + * @throws IOException thrown on I/O error + * @throws DataException thrown on error writing the data for other reasons + * @deprecated use {@link SchematicFormat#MCEDIT} + */ + @Deprecated + public void saveSchematic(File path) throws IOException, DataException { + checkNotNull(path); + SchematicFormat.MCEDIT.save(this, path); + } + + /** + * Load a .schematic file into a clipboard. + * + * @param path the path to the file to load + * @return a clipboard + * @throws IOException thrown on I/O error + * @throws DataException thrown on error writing the data for other reasons + * @deprecated use {@link SchematicFormat#MCEDIT} + */ + @Deprecated + public static CuboidClipboard loadSchematic(File path) throws DataException, IOException { + checkNotNull(path); + return SchematicFormat.MCEDIT.load(path); + } + + /** + * Get the origin point, which corresponds to where the copy was + * originally copied from. The origin is the lowest possible X, Y, and + * Z components of the cuboid region that was copied. + * + * @return the origin + */ + public Vector getOrigin() { + return origin; + } + + /** + * Set the origin point, which corresponds to where the copy was + * originally copied from. The origin is the lowest possible X, Y, and + * Z components of the cuboid region that was copied. + * + * @param origin the origin to set + */ + public void setOrigin(Vector origin) { + checkNotNull(origin); + this.origin = origin; + } + + /** + * Get the offset of the player to the clipboard's minimum point + * (minimum X, Y, Z coordinates). + * + *

The offset is inverse (multiplied by -1).

+ * + * @return the offset the offset + */ + public Vector getOffset() { + return offset; + } + + /** + * Set the offset of the player to the clipboard's minimum point + * (minimum X, Y, Z coordinates). + * + *

The offset is inverse (multiplied by -1).

+ * + * @param offset the new offset + */ + public void setOffset(Vector offset) { + this.offset = offset; + } + + /** + * Get the block distribution inside a clipboard. + * + * @return a block distribution + */ + public List> getBlockDistribution() { + List> distribution = new ArrayList>(); + Map> map = new HashMap>(); + + int maxX = getWidth(); + int maxY = getHeight(); + int maxZ = getLength(); + + for (int x = 0; x < maxX; ++x) { + for (int y = 0; y < maxY; ++y) { + for (int z = 0; z < maxZ; ++z) { + final BaseBlock block = getBlock(x,y,z); + if (block == null) { + continue; + } + + int id = block.getId(); + + if (map.containsKey(id)) { + map.get(id).increment(); + } else { + Countable c = new Countable(id, 1); + map.put(id, c); + distribution.add(c); + } + } + } + } + + Collections.sort(distribution); + // Collections.reverse(distribution); + + return distribution; + } + + /** + * Get the block distribution inside a clipboard with data values. + * + * @return a block distribution + */ + // TODO reduce code duplication + public List> getBlockDistributionWithData() { + List> distribution = new ArrayList>(); + Map> map = new HashMap>(); + + int maxX = getWidth(); + int maxY = getHeight(); + int maxZ = getLength(); + + for (int x = 0; x < maxX; ++x) { + for (int y = 0; y < maxY; ++y) { + for (int z = 0; z < maxZ; ++z) { + final BaseBlock block = getBlock(x,y,z); + if (block == null) { + continue; + } + + // Strip the block from metadata that is not part of our key + final BaseBlock bareBlock = new BaseBlock(block.getId(), block.getData()); + + if (map.containsKey(bareBlock)) { + map.get(bareBlock).increment(); + } else { + Countable c = new Countable(bareBlock, 1); + map.put(bareBlock, c); + distribution.add(c); + } + } + } + } + + Collections.sort(distribution); + // Collections.reverse(distribution); + + return distribution; + } + + /** + * Stores a copied entity. + */ + private class CopiedEntity { + private final LocalEntity entity; + private final Vector relativePosition; + + private CopiedEntity(LocalEntity entity) { + this.entity = entity; + this.relativePosition = entity.getPosition().getPosition().subtract(getOrigin()); + } + } + + public static Class inject() { + return CuboidClipboard.class; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java b/core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java deleted file mode 100644 index 66b5185b..00000000 --- a/core/src/main/java/com/sk89q/worldedit/command/FlattenedClipboardTransform.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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.command; - -import com.sk89q.worldedit.Vector; -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.operation.ForwardExtentCopy; -import com.sk89q.worldedit.function.operation.Operation; -import com.sk89q.worldedit.math.transform.AffineTransform; -import com.sk89q.worldedit.math.transform.CombinedTransform; -import com.sk89q.worldedit.math.transform.Transform; -import com.sk89q.worldedit.regions.CuboidRegion; -import com.sk89q.worldedit.regions.Region; -import com.sk89q.worldedit.world.registry.WorldData; - - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * Helper class to 'bake' a transform into a clipboard. - * - *

This class needs a better name and may need to be made more generic.

- * - * @see Clipboard - * @see Transform - */ -public class FlattenedClipboardTransform { - - private final Clipboard original; - private final Transform transform; - private final WorldData worldData; - - /** - * Create a new instance. - * - * @param original the original clipboard - * @param transform the transform - * @param worldData the world data instance - */ - private FlattenedClipboardTransform(final Clipboard original, final Transform transform, final WorldData worldData) { - checkNotNull(original); - checkNotNull(transform); - checkNotNull(worldData); - this.original = original; - this.transform = transform; - this.worldData = worldData; - } - - /** - * Get the transformed region. - * - * @return the transformed region - */ - public Region getTransformedRegion() { - final Region region = this.original.getRegion(); - final Vector minimum = region.getMinimumPoint(); - final Vector maximum = region.getMaximumPoint(); - - final Transform transformAround = new CombinedTransform(new AffineTransform().translate(this.original.getOrigin().multiply(-1)), this.transform, new AffineTransform().translate(this.original - .getOrigin())); - - final Vector[] corners = new Vector[] { - minimum, - maximum, - minimum.setX(maximum.getX()), - minimum.setY(maximum.getY()), - minimum.setZ(maximum.getZ()), - maximum.setX(minimum.getX()), - maximum.setY(minimum.getY()), - maximum.setZ(minimum.getZ()) }; - - for (int i = 0; i < corners.length; i++) { - corners[i] = transformAround.apply(corners[i]); - } - - Vector newMinimum = corners[0]; - Vector newMaximum = corners[0]; - - for (int i = 1; i < corners.length; i++) { - newMinimum = Vector.getMinimum(newMinimum, corners[i]); - newMaximum = Vector.getMaximum(newMaximum, corners[i]); - } - - // After transformation, the points may not really sit on a block, - // so we should expand the region for edge cases - newMinimum = newMinimum.setX(Math.floor(newMinimum.getX())); - newMinimum = newMinimum.setY(Math.floor(newMinimum.getY())); - newMinimum = newMinimum.setZ(Math.floor(newMinimum.getZ())); - - newMaximum = newMaximum.setX(Math.ceil(newMaximum.getX())); - newMaximum = newMaximum.setY(Math.ceil(newMaximum.getY())); - newMaximum = newMaximum.setZ(Math.ceil(newMaximum.getZ())); - - return new CuboidRegion(newMinimum, newMaximum); - } - - /** - * Create an operation to copy from the original clipboard to the given extent. - * - * @param target the target - * @return the operation - */ - public Operation copyTo(final Extent target) { - final BlockTransformExtent extent = new BlockTransformExtent(this.original, this.transform, this.worldData.getBlockRegistry()); - final ForwardExtentCopy copy = new ForwardExtentCopy(extent, this.original.getRegion(), this.original.getOrigin(), target, this.original.getOrigin()); - copy.setTransform(this.transform); - return copy; - } - - /** - * Create a new instance to bake the transform with. - * - * @param original the original clipboard - * @param transform the transform - * @param worldData the world data instance - * @return a builder - */ - public static FlattenedClipboardTransform transform(final Clipboard original, final Transform transform, final WorldData worldData) { - return new FlattenedClipboardTransform(original, transform, worldData); - } - -} diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java index 4afbd855..2f6cdc61 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java @@ -54,9 +54,7 @@ public class BlockArrayClipboard implements Clipboard { // x,z,y+15>>4 | y&15 private final byte[][] ids; private byte[][] datas; - private final Vector d; - - + private final Vector size; private final HashMap nbtMap; private final List entities = new ArrayList(); @@ -79,10 +77,10 @@ public class BlockArrayClipboard implements Clipboard { public BlockArrayClipboard(Region region) { checkNotNull(region); this.region = region.clone(); - this.d = getDimensions(); - this.dx = d.getBlockX(); - this.dxz = dx * d.getBlockZ(); - ids = new byte[dx * d.getBlockZ() * ((d.getBlockY() + 15) >> 4)][]; + this.size = getDimensions(); + this.dx = size.getBlockX(); + this.dxz = dx * size.getBlockZ(); + ids = new byte[dx * size.getBlockZ() * ((size.getBlockY() + 15) >> 4)][]; nbtMap = new HashMap<>(); this.origin = region.getMinimumPoint(); this.mx = origin.getBlockX(); @@ -249,7 +247,7 @@ public class BlockArrayClipboard implements Clipboard { return true; } if (datas == null) { - datas = new byte[dx * d.getBlockZ() * ((d.getBlockY() + 15) >> 4)][]; + datas = new byte[dx * size.getBlockZ() * ((size.getBlockY() + 15) >> 4)][]; } byte[] dataArray = datas[i]; if (dataArray == null) { @@ -348,7 +346,7 @@ public class BlockArrayClipboard implements Clipboard { return true; } if (datas == null) { - datas = new byte[dx * d.getBlockZ() * ((d.getBlockY() + 15) >> 4)][]; + datas = new byte[dx * size.getBlockZ() * ((size.getBlockY() + 15) >> 4)][]; } byte[] dataArray = datas[i]; if (dataArray == null) { diff --git a/core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java b/core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java new file mode 100644 index 00000000..26732c85 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/extent/transform/BlockTransformExtent.java @@ -0,0 +1,186 @@ +/* + * 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.transform; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.extent.AbstractDelegateExtent; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.transform.Transform; +import com.sk89q.worldedit.world.registry.BlockRegistry; +import com.sk89q.worldedit.world.registry.State; +import com.sk89q.worldedit.world.registry.StateValue; + +import javax.annotation.Nullable; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Transforms blocks themselves (but not their position) according to a + * given transform. + */ +public class BlockTransformExtent extends AbstractDelegateExtent { + + private static final double RIGHT_ANGLE = Math.toRadians(90); + + private final Transform transform; + private final BlockRegistry blockRegistry; + + /** + * Create a new instance. + * + * @param extent the extent + * @param blockRegistry the block registry used for block direction data + */ + public BlockTransformExtent(Extent extent, Transform transform, BlockRegistry blockRegistry) { + super(extent); + checkNotNull(transform); + checkNotNull(blockRegistry); + this.transform = transform; + this.blockRegistry = blockRegistry; + } + + /** + * Get the transform. + * + * @return the transform + */ + public Transform getTransform() { + return transform; + } + + /** + * Transform a block without making a copy. + * + * @param block the block + * @param reverse true to transform in the opposite direction + * @return the same block + */ + private BaseBlock transformBlock(BaseBlock block, boolean reverse) { + return transform(block, reverse ? transform.inverse() : transform, blockRegistry); + } + + @Override + public BaseBlock getBlock(Vector position) { + return transformBlock(super.getBlock(position), false); + } + + @Override + public BaseBlock getLazyBlock(Vector position) { + return transformBlock(super.getLazyBlock(position), false); + } + + @Override + public boolean setBlock(Vector location, BaseBlock block) throws WorldEditException { + return super.setBlock(location, transformBlock(new BaseBlock(block), true)); + } + + + /** + * Transform the given block using the given transform. + * + *

The provided block is modified.

+ * + * @param block the block + * @param transform the transform + * @param registry the registry + * @return the same block + */ + public static BaseBlock transform(BaseBlock block, Transform transform, BlockRegistry registry) { + return transform(block, transform, registry, block); + } + + /** + * Transform the given block using the given transform. + * + * @param block the block + * @param transform the transform + * @param registry the registry + * @param changedBlock the block to change + * @return the changed block + */ + private static BaseBlock transform(BaseBlock block, Transform transform, BlockRegistry registry, BaseBlock changedBlock) { + checkNotNull(block); + checkNotNull(transform); + checkNotNull(registry); + + Map states = registry.getStates(block); + + if (states == null) { + return changedBlock; + } + + for (State state : states.values()) { + if (state.hasDirection()) { + StateValue value = state.getValue(block); + if (value != null && value.getDirection() != null) { + StateValue newValue = getNewStateValue(state, transform, value.getDirection()); + if (newValue != null) { + if (changedBlock.hasWildcardData()) { + changedBlock = new BaseBlock(changedBlock.getId(), changedBlock.getData(), changedBlock.getNbtData()); + } + newValue.set(changedBlock); + } + } + } + } + + return changedBlock; + } + + /** + * Get the new value with the transformed direction. + * + * @param state the state + * @param transform the transform + * @param oldDirection the old direction to transform + * @return a new state or null if none could be found + */ + @Nullable + private static StateValue getNewStateValue(State state, Transform transform, Vector oldDirection) { + Vector newDirection = transform.apply(oldDirection).subtract(transform.apply(Vector.ZERO)).normalize(); + StateValue newValue = null; + double closest = -2; + boolean found = false; + + for (StateValue v : state.valueMap().values()) { + if (v.getDirection() != null) { + double dot = v.getDirection().normalize().dot(newDirection); + if (dot >= closest) { + closest = dot; + newValue = v; + found = true; + } + } + } + + if (found) { + return newValue; + } else { + return null; + } + } + + public static Class inject() { + return BlockTransformExtent.class; + } +} \ No newline at end of file