From 0ce7954dc9512c69a6e7621425eeb7438e15e1f3 Mon Sep 17 00:00:00 2001 From: sk89q Date: Thu, 10 Jul 2014 22:22:35 -0700 Subject: [PATCH] Add support for copying entities between Extents. --- .../sk89q/worldedit/bukkit/BukkitWorld.java | 43 +++-- .../sk89q/worldedit/forge/ForgeEntity.java | 42 ++++- .../com/sk89q/worldedit/forge/ForgeWorld.java | 40 ++++- .../java/com/sk89q/worldedit/EditSession.java | 18 +- src/main/java/com/sk89q/worldedit/Vector.java | 35 ++++ .../extent/AbstractDelegateExtent.java | 8 +- .../worldedit/extent/ChangeSetExtent.java | 72 ++++++++ .../com/sk89q/worldedit/extent/Extent.java | 14 +- .../sk89q/worldedit/extent/NullExtent.java | 6 + .../extent/clipboard/BlockArrayClipboard.java | 16 +- .../function/entity/ExtentEntityCopy.java | 103 ++++++++++++ .../function/operation/ForwardExtentCopy.java | 42 ++++- .../function/visitor/EntityVisitor.java | 4 +- .../history/change/EntityCreate.java | 68 ++++++++ .../history/change/EntityRemove.java | 65 +++++++ .../worldedit/internal/LocalWorldAdapter.java | 12 +- .../internal/helper/MCDirections.java | 62 +++++++ .../com/sk89q/worldedit/util/Direction.java | 159 ++++++++++++++++++ .../com/sk89q/worldedit/util/Location.java | 22 +-- .../sk89q/worldedit/world/AbstractWorld.java | 13 +- .../com/sk89q/worldedit/world/NullWorld.java | 5 + .../java/com/sk89q/worldedit/world/World.java | 8 - .../sk89q/worldedit/util/LocationTest.java | 19 --- 23 files changed, 768 insertions(+), 108 deletions(-) create mode 100644 src/main/java/com/sk89q/worldedit/function/entity/ExtentEntityCopy.java create mode 100644 src/main/java/com/sk89q/worldedit/history/change/EntityCreate.java create mode 100644 src/main/java/com/sk89q/worldedit/history/change/EntityRemove.java create mode 100644 src/main/java/com/sk89q/worldedit/internal/helper/MCDirections.java create mode 100644 src/main/java/com/sk89q/worldedit/util/Direction.java diff --git a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index 72a774ede..e693f18ea 100644 --- a/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/src/bukkit/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -39,7 +39,6 @@ import com.sk89q.worldedit.blocks.MobSpawnerBlock; import com.sk89q.worldedit.blocks.NoteBlock; import com.sk89q.worldedit.blocks.SignBlock; import com.sk89q.worldedit.blocks.SkullBlock; -import com.sk89q.worldedit.bukkit.entity.BukkitEntity; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.TreeGenerator; @@ -222,6 +221,26 @@ public class BukkitWorld extends LocalWorld { } } + @Override + public List getEntities(Region region) { + World world = getWorld(); + + List entities = new ArrayList(); + for (Vector2D pt : region.getChunks()) { + if (!world.isChunkLoaded(pt.getBlockX(), pt.getBlockZ())) { + continue; + } + + final Entity[] ents = world.getChunkAt(pt.getBlockX(), pt.getBlockZ()).getEntities(); + for (Entity ent : ents) { + if (region.contains(BukkitUtil.toVector(ent.getLocation()))) { + entities.add(BukkitAdapter.adapt(ent)); + } + } + } + return entities; + } + @Override public List getEntities() { List list = new ArrayList(); @@ -1240,26 +1259,6 @@ public class BukkitWorld extends LocalWorld { getWorld().getBlockAt(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()).breakNaturally(); } - @Override - public LocalEntity[] getEntities(Region region) { - World world = getWorld(); - - List entities = new ArrayList(); - for (Vector2D pt : region.getChunks()) { - if (!world.isChunkLoaded(pt.getBlockX(), pt.getBlockZ())) { - continue; - } - - final Entity[] ents = world.getChunkAt(pt.getBlockX(), pt.getBlockZ()).getEntities(); - for (Entity ent : ents) { - if (region.contains(BukkitUtil.toVector(ent.getLocation()))) { - entities.add(BukkitUtil.toLocalEntity(ent)); - } - } - } - return entities.toArray(new BukkitEntity[entities.size()]); - } - @Override public int killEntities(LocalEntity... entities) { World world = getWorld(); @@ -1267,7 +1266,7 @@ public class BukkitWorld extends LocalWorld { int amount = 0; Set toKill = new HashSet(); for (LocalEntity entity : entities) { - toKill.add(((BukkitEntity) entity).getEntityId()); + toKill.add(((com.sk89q.worldedit.bukkit.entity.BukkitEntity) entity).getEntityId()); } for (Entity entity : world.getEntities()) { if (toKill.contains(entity.getUniqueId())) { diff --git a/src/forge/java/com/sk89q/worldedit/forge/ForgeEntity.java b/src/forge/java/com/sk89q/worldedit/forge/ForgeEntity.java index 75f1d9b09..4a55e41bd 100644 --- a/src/forge/java/com/sk89q/worldedit/forge/ForgeEntity.java +++ b/src/forge/java/com/sk89q/worldedit/forge/ForgeEntity.java @@ -23,7 +23,10 @@ import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.helper.MCDirections; +import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.Location; +import net.minecraft.entity.EntityHanging; import net.minecraft.entity.EntityList; import net.minecraft.nbt.NBTTagCompound; @@ -40,17 +43,42 @@ class ForgeEntity implements Entity { @Override public BaseEntity getState() { - NBTTagCompound tag = new NBTTagCompound(); - entity.writeToNBT(tag); - return new BaseEntity(EntityList.getEntityString(entity), NBTConverter.fromNative(tag)); + String id = EntityList.getEntityString(entity); + if (id != null) { + NBTTagCompound tag = new NBTTagCompound(); + entity.writeToNBT(tag); + return new BaseEntity(id, NBTConverter.fromNative(tag)); + } else { + return null; + } } @Override public Location getLocation() { - return new Location( - ForgeAdapter.adapt(entity.worldObj), - new Vector(entity.posX, entity.posY, entity.posZ), - ForgeAdapter.adapt(entity.getLookVec())); + Vector position; + float pitch; + float yaw; + + if (entity instanceof EntityHanging) { + EntityHanging hanging = (EntityHanging) entity; + + position = new Vector(hanging.xPosition, hanging.yPosition, hanging.zPosition); + + Direction direction = MCDirections.fromHanging(hanging.hangingDirection); + if (direction != null) { + yaw = direction.toVector().toYaw(); + pitch = direction.toVector().toPitch(); + } else { + yaw = (float) Math.toRadians(entity.rotationYaw); + pitch = (float) Math.toRadians(entity.rotationPitch); + } + } else { + position = new Vector(entity.posX, entity.posY, entity.posZ); + yaw = (float) Math.toRadians(entity.rotationYaw); + pitch = (float) Math.toRadians(entity.rotationPitch); + } + + return new Location(ForgeAdapter.adapt(entity.worldObj), position, yaw, pitch); } @Override diff --git a/src/forge/java/com/sk89q/worldedit/forge/ForgeWorld.java b/src/forge/java/com/sk89q/worldedit/forge/ForgeWorld.java index 5087799a7..7c68e6cdf 100644 --- a/src/forge/java/com/sk89q/worldedit/forge/ForgeWorld.java +++ b/src/forge/java/com/sk89q/worldedit/forge/ForgeWorld.java @@ -32,7 +32,10 @@ import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.blocks.LazyBlock; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.internal.helper.MCDirections; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.Direction.Flag; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.TreeGenerator.TreeType; import com.sk89q.worldedit.world.AbstractWorld; @@ -500,7 +503,29 @@ public class ForgeWorld extends AbstractWorld { } @Override - public List getEntities() { + @SuppressWarnings("unchecked") + public List getEntities(Region region) { + List entities = new ArrayList(); + World world = getWorld(); + for (Vector2D pt : region.getChunks()) { + if (!world.getChunkProvider().chunkExists(pt.getBlockX(), pt.getBlockZ())) { + continue; + } + + Chunk chunk = world.getChunkProvider().provideChunk(pt.getBlockX(), pt.getBlockZ()); + for (List entitySubList : chunk.entityLists) { + for (net.minecraft.entity.Entity entity : entitySubList) { + if (region.contains(new Vector(entity.posX, entity.posY, entity.posZ))) { + entities.add(new ForgeEntity(entity)); + } + } + } + } + return entities; + } + + @Override + public List getEntities() { List entities = new ArrayList(); for (Object entity : getWorld().getLoadedEntityList()) { entities.add(new ForgeEntity((net.minecraft.entity.Entity) entity)); @@ -518,6 +543,19 @@ public class ForgeWorld extends AbstractWorld { if (tag != null) { createdEntity.readFromNBT(NBTConverter.toNative(entity.getNbtData())); } + + createdEntity.setLocationAndAngles(location.getX(), location.getY(), location.getZ(), (float) Math.toDegrees(location.getYaw()), (float) Math.toDegrees(location.getPitch())); + + // Special handling for hanging entities + if (createdEntity instanceof EntityHanging) { + EntityHanging hanging = (EntityHanging) createdEntity; + hanging.xPosition = location.getBlockX(); + hanging.yPosition = location.getBlockY(); + hanging.zPosition = location.getBlockZ(); + Direction direction = Direction.findClosest(location.getDirection(), Flag.CARDINAL); + hanging.setDirection(MCDirections.toHanging(direction)); + } + world.spawnEntityInWorld(createdEntity); return new ForgeEntity(createdEntity); } else { diff --git a/src/main/java/com/sk89q/worldedit/EditSession.java b/src/main/java/com/sk89q/worldedit/EditSession.java index 7ad6b867f..10a849a17 100644 --- a/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/src/main/java/com/sk89q/worldedit/EditSession.java @@ -582,15 +582,10 @@ public class EditSession implements Extent { return getBlock(position).isAir() && setBlock(position, block); } - @Override - public List getEntities() { - return world.getEntities(); - } - @Override @Nullable public Entity createEntity(com.sk89q.worldedit.util.Location location, BaseEntity entity) { - return world.createEntity(location, entity); + return bypassNone.createEntity(location, entity); } /** @@ -649,6 +644,16 @@ public class EditSession implements Extent { return getWorld().getMaximumPoint(); } + @Override + public List getEntities(Region region) { + return bypassNone.getEntities(region); + } + + @Override + public List getEntities() { + return bypassNone.getEntities(); + } + /** * Finish off the queue. */ @@ -1160,6 +1165,7 @@ public class EditSession implements Extent { ForwardExtentCopy copy = new ForwardExtentCopy(this, region, buffer, to); copy.setTransform(new AffineTransform().translate(dir.multiply(distance))); copy.setSourceFunction(remove); // Remove + copy.setRemovingEntities(true); if (!copyAir) { copy.setSourceMask(new ExistingBlockMask(this)); } diff --git a/src/main/java/com/sk89q/worldedit/Vector.java b/src/main/java/com/sk89q/worldedit/Vector.java index c028c5797..5585e9961 100644 --- a/src/main/java/com/sk89q/worldedit/Vector.java +++ b/src/main/java/com/sk89q/worldedit/Vector.java @@ -652,6 +652,40 @@ public class Vector implements Comparable { throw new RuntimeException("This should not happen"); } + /** + * Get this vector's pitch as used within the game. + * + * @return pitch in radians + */ + public float toPitch() { + double x = getX(); + double z = getZ(); + + if (x == 0 && z == 0) { + return getY() > 0 ? -90 : 90; + } else { + double x2 = x * x; + double z2 = z * z; + double xz = Math.sqrt(x2 + z2); + return (float) Math.atan(-getY() / xz); + } + } + + /** + * Get this vector's yaw as used within the game. + * + * @return yaw in radians + */ + public float toYaw() { + double x = getX(); + double z = getZ(); + + double t = Math.atan2(-x, z); + double _2pi = 2 * Math.PI; + + return (float) ((t + _2pi) % _2pi); + } + /** * Get a block point from a point. * @@ -792,4 +826,5 @@ public class Vector implements Comparable { (v1.z + v2.z) / 2 ); } + } diff --git a/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java b/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java index 666c33872..65399bbf4 100644 --- a/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java +++ b/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java @@ -27,6 +27,7 @@ import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.OperationQueue; import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.regions.Region; import javax.annotation.Nullable; @@ -82,10 +83,15 @@ public abstract class AbstractDelegateExtent implements Extent { } @Override - public List getEntities() { + public List getEntities() { return extent.getEntities(); } + @Override + public List getEntities(Region region) { + return extent.getEntities(region); + } + @Override public Vector getMinimumPoint() { return extent.getMinimumPoint(); diff --git a/src/main/java/com/sk89q/worldedit/extent/ChangeSetExtent.java b/src/main/java/com/sk89q/worldedit/extent/ChangeSetExtent.java index 87dfeac3d..772d8ca57 100644 --- a/src/main/java/com/sk89q/worldedit/extent/ChangeSetExtent.java +++ b/src/main/java/com/sk89q/worldedit/extent/ChangeSetExtent.java @@ -22,8 +22,19 @@ package com.sk89q.worldedit.extent; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.history.change.BlockChange; +import com.sk89q.worldedit.history.change.EntityCreate; +import com.sk89q.worldedit.history.change.EntityRemove; import com.sk89q.worldedit.history.changeset.ChangeSet; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Location; + +import javax.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; @@ -53,4 +64,65 @@ public class ChangeSetExtent extends AbstractDelegateExtent { return super.setBlock(location, block); } + @Nullable + @Override + public Entity createEntity(Location location, BaseEntity state) { + Entity entity = super.createEntity(location, state); + if (state != null) { + changeSet.add(new EntityCreate(location, state, entity)); + } + return entity; + } + + @Override + public List getEntities() { + return wrapEntities(super.getEntities()); + } + + @Override + public List getEntities(Region region) { + return wrapEntities(super.getEntities(region)); + } + + private List wrapEntities(List entities) { + List newList = new ArrayList(entities.size()); + for (Entity entity : entities) { + newList.add(new TrackedEntity(entity)); + } + return newList; + } + + private class TrackedEntity implements Entity { + private final Entity entity; + + private TrackedEntity(Entity entity) { + this.entity = entity; + } + + @Override + public BaseEntity getState() { + return entity.getState(); + } + + @Override + public Location getLocation() { + return entity.getLocation(); + } + + @Override + public Extent getExtent() { + return entity.getExtent(); + } + + @Override + public boolean remove() { + Location location = entity.getLocation(); + BaseEntity state = entity.getState(); + boolean success = entity.remove(); + if (state != null && success) { + changeSet.add(new EntityRemove(location, state)); + } + return success; + } + } } diff --git a/src/main/java/com/sk89q/worldedit/extent/Extent.java b/src/main/java/com/sk89q/worldedit/extent/Extent.java index 14bacb425..51ae4fc84 100644 --- a/src/main/java/com/sk89q/worldedit/extent/Extent.java +++ b/src/main/java/com/sk89q/worldedit/extent/Extent.java @@ -23,6 +23,7 @@ import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.regions.Region; import javax.annotation.Nullable; import java.util.List; @@ -56,6 +57,17 @@ public interface Extent extends InputExtent, OutputExtent { */ Vector getMaximumPoint(); + /** + * Get a list of all entities within the given region. + *

+ * If the extent is not wholly loaded (i.e. a world being simulated in the + * game will not have every chunk loaded), then this list may not be + * incomplete. + * + * @return a list of entities + */ + List getEntities(Region region); + /** * Get a list of all entities. *

@@ -65,7 +77,7 @@ public interface Extent extends InputExtent, OutputExtent { * * @return a list of entities */ - List getEntities(); + List getEntities(); /** * Create an entity at the given location. diff --git a/src/main/java/com/sk89q/worldedit/extent/NullExtent.java b/src/main/java/com/sk89q/worldedit/extent/NullExtent.java index 5f8f84644..3df4879de 100644 --- a/src/main/java/com/sk89q/worldedit/extent/NullExtent.java +++ b/src/main/java/com/sk89q/worldedit/extent/NullExtent.java @@ -26,6 +26,7 @@ import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.regions.Region; import javax.annotation.Nullable; import java.util.Collections; @@ -49,6 +50,11 @@ public class NullExtent implements Extent { return nullPoint; } + @Override + public List getEntities(Region region) { + return Collections.emptyList(); + } + @Override public List getEntities() { return Collections.emptyList(); diff --git a/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java b/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java index 5c40d42e2..33be36453 100644 --- a/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java +++ b/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java @@ -31,6 +31,7 @@ import com.sk89q.worldedit.util.Location; import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; @@ -93,8 +94,19 @@ public class BlockArrayClipboard implements Clipboard { } @Override - public List getEntities() { - return new ArrayList(entities); + public List getEntities(Region region) { + List filtered = new ArrayList(); + for (Entity entity : entities) { + if (region.contains(entity.getLocation().toVector())) { + filtered.add(entity); + } + } + return Collections.unmodifiableList(filtered); + } + + @Override + public List getEntities() { + return Collections.unmodifiableList(entities); } @Nullable diff --git a/src/main/java/com/sk89q/worldedit/function/entity/ExtentEntityCopy.java b/src/main/java/com/sk89q/worldedit/function/entity/ExtentEntityCopy.java new file mode 100644 index 000000000..2728285fe --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/function/entity/ExtentEntityCopy.java @@ -0,0 +1,103 @@ +/* + * 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.entity; + +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.EntityFunction; +import com.sk89q.worldedit.math.transform.Transform; +import com.sk89q.worldedit.util.Location; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Copies entities provided to the function to the provided destination + * {@code Extent}. + */ +public class ExtentEntityCopy implements EntityFunction { + + private final Extent destination; + private final Vector from; + private final Vector to; + private final Transform transform; + private boolean removing; + + /** + * Create a new instance. + * + * @param from the from position + * @param destination the destination {@code Extent} + * @param to the destination position + * @param transform the transformation to apply to both position and orientation + */ + public ExtentEntityCopy(Vector from, Extent destination, Vector to, Transform transform) { + checkNotNull(from); + checkNotNull(destination); + checkNotNull(to); + checkNotNull(transform); + this.destination = destination; + this.from = from; + this.to = to; + this.transform = transform; + } + + /** + * Return whether entities that are copied should be removed. + * + * @return true if removing + */ + public boolean isRemoving() { + return removing; + } + + /** + * Set whether entities that are copied should be removed. + * + * @param removing true if removing + */ + public void setRemoving(boolean removing) { + this.removing = removing; + } + + @Override + public boolean apply(Entity entity) throws WorldEditException { + BaseEntity state = entity.getState(); + if (state != null) { + Location location = entity.getLocation(); + Vector newPosition = transform.apply(location.toVector().subtract(from)); + Vector newDirection = transform.apply(location.getDirection()).subtract(transform.apply(Vector.ZERO)).normalize(); + Location newLocation = new Location(destination, newPosition.add(to), newDirection); + boolean success = destination.createEntity(newLocation, state) != null; + + // Remove + if (isRemoving() && success) { + entity.remove(); + } + + return success; + } else { + return false; + } + } + +} diff --git a/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java b/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java index ce0f80248..3f9887b28 100644 --- a/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java +++ b/src/main/java/com/sk89q/worldedit/function/operation/ForwardExtentCopy.java @@ -19,20 +19,25 @@ package com.sk89q.worldedit.function.operation; -import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.CombinedRegionFunction; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.RegionMaskingFilter; 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.Masks; +import com.sk89q.worldedit.function.visitor.EntityVisitor; import com.sk89q.worldedit.function.visitor.RegionVisitor; import com.sk89q.worldedit.math.transform.Identity; import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.regions.Region; +import java.util.List; + import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -52,6 +57,7 @@ public class ForwardExtentCopy implements Operation { private final Vector to; private int repetitions = 1; private Mask sourceMask = Masks.alwaysTrue(); + private boolean removingEntities; private RegionFunction sourceFunction = null; private Transform transform = new Identity(); private Transform currentTransform = null; @@ -177,6 +183,24 @@ public class ForwardExtentCopy implements Operation { this.repetitions = repetitions; } + /** + * Return whether entities that are copied should be removed. + * + * @return true if removing + */ + public boolean isRemovingEntities() { + return removingEntities; + } + + /** + * Set whether entities that are copied should be removed. + * + * @param removing true if removing + */ + public void setRemovingEntities(boolean removingEntities) { + this.removingEntities = removingEntities; + } + /** * Get the number of affected objects. * @@ -200,13 +224,19 @@ public class ForwardExtentCopy implements Operation { currentTransform = transform; } - ExtentBlockCopy copy = new ExtentBlockCopy(source, from, destination, to, currentTransform); - RegionMaskingFilter filter = new RegionMaskingFilter(sourceMask, copy); + ExtentBlockCopy blockCopy = new ExtentBlockCopy(source, from, destination, to, currentTransform); + RegionMaskingFilter filter = new RegionMaskingFilter(sourceMask, blockCopy); RegionFunction function = sourceFunction != null ? new CombinedRegionFunction(filter, sourceFunction) : filter; - RegionVisitor visitor = new RegionVisitor(region, function); - lastVisitor = visitor; + RegionVisitor blockVisitor = new RegionVisitor(region, function); + + ExtentEntityCopy entityCopy = new ExtentEntityCopy(from, destination, to, currentTransform); + entityCopy.setRemoving(removingEntities); + List entities = source.getEntities(region); + EntityVisitor entityVisitor = new EntityVisitor(entities.iterator(), entityCopy); + + lastVisitor = blockVisitor; currentTransform = currentTransform.combine(transform); - return new DelegateOperation(this, visitor); + return new DelegateOperation(this, new OperationQueue(blockVisitor, entityVisitor)); } else { return null; } diff --git a/src/main/java/com/sk89q/worldedit/function/visitor/EntityVisitor.java b/src/main/java/com/sk89q/worldedit/function/visitor/EntityVisitor.java index 14509bd8c..d412103e9 100644 --- a/src/main/java/com/sk89q/worldedit/function/visitor/EntityVisitor.java +++ b/src/main/java/com/sk89q/worldedit/function/visitor/EntityVisitor.java @@ -34,7 +34,7 @@ import static com.google.common.base.Preconditions.checkNotNull; */ public class EntityVisitor implements Operation { - private final Iterator iterator; + private final Iterator iterator; private final EntityFunction function; private int affected = 0; @@ -44,7 +44,7 @@ public class EntityVisitor implements Operation { * @param iterator the iterator * @param function the function */ - public EntityVisitor(Iterator iterator, EntityFunction function) { + public EntityVisitor(Iterator iterator, EntityFunction function) { checkNotNull(iterator); checkNotNull(function); this.iterator = iterator; diff --git a/src/main/java/com/sk89q/worldedit/history/change/EntityCreate.java b/src/main/java/com/sk89q/worldedit/history/change/EntityCreate.java new file mode 100644 index 000000000..80d0bbcaa --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/history/change/EntityCreate.java @@ -0,0 +1,68 @@ +/* + * 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.history.change; + +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.history.UndoContext; +import com.sk89q.worldedit.util.Location; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Logs the creation of an entity and removes the entity upon undo. + */ +public class EntityCreate implements Change { + + private final Location location; + private final BaseEntity state; + private Entity entity; + + /** + * Create a new instance. + * + * @param location the location + * @param state the state of the created entity + * @param entity the entity that was created + */ + public EntityCreate(Location location, BaseEntity state, Entity entity) { + checkNotNull(location); + checkNotNull(state); + checkNotNull(entity); + this.location = location; + this.state = state; + this.entity = entity; + } + + @Override + public void undo(UndoContext context) throws WorldEditException { + if (entity != null) { + entity.remove(); + entity = null; + } + } + + @Override + public void redo(UndoContext context) throws WorldEditException { + entity = checkNotNull(context.getExtent()).createEntity(location, state); + } + +} diff --git a/src/main/java/com/sk89q/worldedit/history/change/EntityRemove.java b/src/main/java/com/sk89q/worldedit/history/change/EntityRemove.java new file mode 100644 index 000000000..eef480a91 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/history/change/EntityRemove.java @@ -0,0 +1,65 @@ +/* + * 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.history.change; + +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.history.UndoContext; +import com.sk89q.worldedit.util.Location; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Tracks the removal of an entity. + */ +public class EntityRemove implements Change { + + private final Location location; + private final BaseEntity state; + private Entity entity; + + /** + * Create a new instance. + * + * @param location the location + * @param state the state of the created entity + */ + public EntityRemove(Location location, BaseEntity state) { + checkNotNull(location); + checkNotNull(state); + this.location = location; + this.state = state; + } + + @Override + public void undo(UndoContext context) throws WorldEditException { + entity = checkNotNull(context.getExtent()).createEntity(location, state); + } + + @Override + public void redo(UndoContext context) throws WorldEditException { + if (entity != null) { + entity.remove(); + entity = null; + } + } + +} diff --git a/src/main/java/com/sk89q/worldedit/internal/LocalWorldAdapter.java b/src/main/java/com/sk89q/worldedit/internal/LocalWorldAdapter.java index dd04767b5..2166b506d 100644 --- a/src/main/java/com/sk89q/worldedit/internal/LocalWorldAdapter.java +++ b/src/main/java/com/sk89q/worldedit/internal/LocalWorldAdapter.java @@ -171,11 +171,6 @@ public class LocalWorldAdapter extends LocalWorld { world.simulateBlockMine(position); } - @Override - public LocalEntity[] getEntities(Region region) { - return world.getEntities(region); - } - @Override public int killEntities(LocalEntity... entity) { return world.killEntities(entity); @@ -293,6 +288,11 @@ public class LocalWorldAdapter extends LocalWorld { return world.getMaximumPoint(); } + @Override + public List getEntities(Region region) { + return world.getEntities(region); + } + @Override public BaseBlock getBlock(Vector position) { return world.getBlock(position); @@ -335,7 +335,7 @@ public class LocalWorldAdapter extends LocalWorld { } @Override - public List getEntities() { + public List getEntities() { return world.getEntities(); } diff --git a/src/main/java/com/sk89q/worldedit/internal/helper/MCDirections.java b/src/main/java/com/sk89q/worldedit/internal/helper/MCDirections.java new file mode 100644 index 000000000..6805196de --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/internal/helper/MCDirections.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.internal.helper; + +import com.sk89q.worldedit.util.Direction; + +/** + * Utility methods for working with directions in Minecraft. + */ +public final class MCDirections { + + private MCDirections() { + } + + public static Direction fromHanging(int i) { + switch (i) { + case 0: + return Direction.SOUTH; + case 1: + return Direction.WEST; + case 2: + return Direction.NORTH; + case 3: + return Direction.EAST; + default: + return Direction.NORTH; + } + } + + 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; + } + } + +} diff --git a/src/main/java/com/sk89q/worldedit/util/Direction.java b/src/main/java/com/sk89q/worldedit/util/Direction.java new file mode 100644 index 000000000..bf1997f19 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/util/Direction.java @@ -0,0 +1,159 @@ +/* + * 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.util; + +import com.sk89q.worldedit.Vector; + +import javax.annotation.Nullable; + +/** + * A collection of cardinal, ordinal, and secondary-ordinal directions. + */ +public enum Direction { + + NORTH(new Vector(0, 0, -1), Flag.CARDINAL), + EAST(new Vector(1, 0, 0), Flag.CARDINAL), + SOUTH(new Vector(0, 0, 1), Flag.CARDINAL), + WEST(new Vector(-1, 0, 0), Flag.CARDINAL), + + UP(new Vector(0, 1, 0), Flag.UPRIGHT), + DOWN(new Vector(0, -1, 0), Flag.UPRIGHT), + + NORTHEAST(new Vector(1, 0, -1), Flag.ORDINAL), + NORTHWEST(new Vector(-1, 0, -1), Flag.ORDINAL), + SOUTHEAST(new Vector(1, 0, 1), Flag.ORDINAL), + SOUTHWEST(new Vector(-1, 0, 1), Flag.ORDINAL), + + WEST_NORTHWEST(new Vector(-Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL), + WEST_SOUTHWEST(new Vector(-Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL), + NORTH_NORTHWEST(new Vector(Math.sin(Math.PI / 8), 0, -Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL), + NORTH_NORTHEAST(new Vector(Math.sin(Math.PI / 8), 0, -Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL), + EAST_NORTHEAST(new Vector(Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL), + EAST_SOUTHEAST(new Vector(Math.cos(Math.PI / 8), 0, Math.sin(Math.PI / 8)), Flag.SECONDARY_ORDINAL), + SOUTH_SOUTHEAST(new Vector(Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL), + SOUTH_SOUTHWEST(new Vector(Math.sin(Math.PI / 8), 0, Math.cos(Math.PI / 8)), Flag.SECONDARY_ORDINAL); + + private final Vector direction; + private final int flags; + + private Direction(Vector vector, int flags) { + this.direction = vector.normalize(); + this.flags = flags; + } + + /** + * Return true if the direction is of a cardinal direction (north, west + * east, and south). + * + *

This evaluates as false for directions that have a non-zero + * Y-component.

+ * + * @return true if cardinal + */ + public boolean isCardinal() { + return (flags & Flag.CARDINAL) > 0; + } + + /** + * Return true if the direction is of an ordinal direction (northwest, + * southwest, southeast, northeaast). + * + * @return true if ordinal + */ + public boolean isOrdinal() { + return (flags & Flag.ORDINAL) > 0; + } + + /** + * Return true if the direction is of a secondary ordinal direction + * (north-northwest, north-northeast, south-southwest, etc.). + * + * @return true if secondary ordinal + */ + public boolean isSecondaryOrdinal() { + return (flags & Flag.SECONDARY_ORDINAL) > 0; + } + + /** + * Return whether Y component is non-zero. + * + * @return true if the Y component is non-zero + */ + public boolean isUpright() { + return (flags & Flag.UPRIGHT) > 0; + } + + /** + * Get the vector. + * + * @return the vector + */ + public Vector toVector() { + return direction; + } + + /** + * Find the closest direction to the given direction vector. + * + * @param vector the vector + * @param flags the only flags that are permitted (use bitwise math) + * @return the closest direction, or null if no direction can be returned + */ + @Nullable + public static Direction findClosest(Vector vector, int flags) { + if ((flags & Flag.UPRIGHT) == 0) { + vector = vector.setY(0); + } + vector = vector.normalize(); + + Direction closest = null; + double closestDot = -2; + for (Direction direction : values()) { + if ((~flags & direction.flags) > 0) { + continue; + } + + double dot = direction.toVector().dot(vector); + if (dot >= closestDot) { + closest = direction; + closestDot = dot; + } + } + + return closest; + } + + /** + * Flags to use with {@link #findClosest(Vector, int)}. + */ + public static final class Flag { + public static int CARDINAL = 0x1; + public static int ORDINAL = 0x2; + public static int SECONDARY_ORDINAL = 0x4; + public static int UPRIGHT = 0x8; + + public static int ALL = CARDINAL | ORDINAL | SECONDARY_ORDINAL | UPRIGHT; + + private Flag() { + } + } + +} + diff --git a/src/main/java/com/sk89q/worldedit/util/Location.java b/src/main/java/com/sk89q/worldedit/util/Location.java index 0bc45e592..e1b6ee487 100644 --- a/src/main/java/com/sk89q/worldedit/util/Location.java +++ b/src/main/java/com/sk89q/worldedit/util/Location.java @@ -113,8 +113,7 @@ public class Location { * @param direction the direction vector */ public Location(Extent extent, Vector position, Vector direction) { - this(extent, position, 0, 0); - this.setDirection(direction); + this(extent, position, direction.toYaw(), direction.toPitch()); } /** @@ -194,24 +193,7 @@ public class Location { * @return the new instance */ public Location setDirection(Vector direction) { - double x = direction.getX(); - double z = direction.getZ(); - - if (x == 0 && z == 0) { - float pitch = direction.getY() > 0 ? -90 : 90; - return new Location(extent, position, 0, pitch); - } else { - double t = Math.atan2(-x, z); - double x2 = x * x; - double z2 = z * z; - double xz = Math.sqrt(x2 + z2); - double _2pi = 2 * Math.PI; - - float pitch = (float) Math.atan(-direction.getY() / xz); - float yaw = (float) ((t + _2pi) % _2pi); - - return new Location(extent, position, yaw, pitch); - } + return new Location(extent, position, direction.toYaw(), direction.toPitch()); } /** diff --git a/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java b/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java index e5876107c..6c982cf6a 100644 --- a/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java +++ b/src/main/java/com/sk89q/worldedit/world/AbstractWorld.java @@ -19,8 +19,13 @@ package com.sk89q.worldedit.world; -import com.sk89q.worldedit.*; +import com.sk89q.worldedit.BlockVector2D; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.LocalEntity; import com.sk89q.worldedit.LocalWorld.KillFlags; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.blocks.BlockID; @@ -29,7 +34,6 @@ import com.sk89q.worldedit.extension.platform.Platform; import com.sk89q.worldedit.function.mask.BlockMask; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.operation.Operation; -import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.TreeGenerator.TreeType; import javax.annotation.Nullable; @@ -156,11 +160,6 @@ public abstract class AbstractWorld implements World { } } - @Override - public LocalEntity[] getEntities(Region region) { - return new LocalEntity[0]; - } - @Override public int killEntities(LocalEntity... entities) { return 0; diff --git a/src/main/java/com/sk89q/worldedit/world/NullWorld.java b/src/main/java/com/sk89q/worldedit/world/NullWorld.java index 937ba9c3b..a4f7a5eed 100644 --- a/src/main/java/com/sk89q/worldedit/world/NullWorld.java +++ b/src/main/java/com/sk89q/worldedit/world/NullWorld.java @@ -109,6 +109,11 @@ public class NullWorld extends AbstractWorld { return new BaseBlock(BlockID.AIR); } + @Override + public List getEntities(Region region) { + return Collections.emptyList(); + } + @Override public List getEntities() { return Collections.emptyList(); diff --git a/src/main/java/com/sk89q/worldedit/world/World.java b/src/main/java/com/sk89q/worldedit/world/World.java index eb12f7f71..1c077d3d2 100644 --- a/src/main/java/com/sk89q/worldedit/world/World.java +++ b/src/main/java/com/sk89q/worldedit/world/World.java @@ -209,14 +209,6 @@ public interface World extends Extent { */ void simulateBlockMine(Vector position); - /** - * Get a list of entities in the given region. - * - * @param region the region - * @return a list of entities - */ - LocalEntity[] getEntities(Region region); - /** * Kill the entities listed in the provided array. * diff --git a/src/test/java/com/sk89q/worldedit/util/LocationTest.java b/src/test/java/com/sk89q/worldedit/util/LocationTest.java index 06d546e32..1acdbc892 100644 --- a/src/test/java/com/sk89q/worldedit/util/LocationTest.java +++ b/src/test/java/com/sk89q/worldedit/util/LocationTest.java @@ -51,25 +51,6 @@ public class LocationTest { assertEquals(world2, location2.getExtent()); } - @Test - public void testGetDirection() throws Exception { - World world = mock(World.class); - Vector direction = new Vector(1, 1, 1); - Location location = new Location(world, new Vector(), direction); - assertEquals(direction, location.getDirection()); - } - - @Test - public void testSetDirection() throws Exception { - World world = mock(World.class); - Vector direction1 = new Vector(1, 1, 1); - Vector direction2 = new Vector(2, 2, 2); - Location location1 = new Location(world, new Vector(), direction1); - Location location2 = location1.setDirection(direction2); - assertEquals(direction1, location1.getDirection()); - assertEquals(direction2, location2.getDirection()); - } - @Test public void testToVector() throws Exception { World world = mock(World.class);