Add support for copying entities between Extents.

This commit is contained in:
sk89q 2014-07-10 22:22:35 -07:00
parent 52f1a7d2d4
commit 0ce7954dc9
23 changed files with 768 additions and 108 deletions

View File

@ -39,7 +39,6 @@ import com.sk89q.worldedit.blocks.MobSpawnerBlock;
import com.sk89q.worldedit.blocks.NoteBlock; import com.sk89q.worldedit.blocks.NoteBlock;
import com.sk89q.worldedit.blocks.SignBlock; import com.sk89q.worldedit.blocks.SignBlock;
import com.sk89q.worldedit.blocks.SkullBlock; import com.sk89q.worldedit.blocks.SkullBlock;
import com.sk89q.worldedit.bukkit.entity.BukkitEntity;
import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.util.TreeGenerator;
@ -222,6 +221,26 @@ public class BukkitWorld extends LocalWorld {
} }
} }
@Override
public List<com.sk89q.worldedit.entity.Entity> getEntities(Region region) {
World world = getWorld();
List<com.sk89q.worldedit.entity.Entity> entities = new ArrayList<com.sk89q.worldedit.entity.Entity>();
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 @Override
public List<com.sk89q.worldedit.entity.Entity> getEntities() { public List<com.sk89q.worldedit.entity.Entity> getEntities() {
List<com.sk89q.worldedit.entity.Entity> list = new ArrayList<com.sk89q.worldedit.entity.Entity>(); List<com.sk89q.worldedit.entity.Entity> list = new ArrayList<com.sk89q.worldedit.entity.Entity>();
@ -1240,26 +1259,6 @@ public class BukkitWorld extends LocalWorld {
getWorld().getBlockAt(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()).breakNaturally(); getWorld().getBlockAt(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()).breakNaturally();
} }
@Override
public LocalEntity[] getEntities(Region region) {
World world = getWorld();
List<BukkitEntity> entities = new ArrayList<BukkitEntity>();
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 @Override
public int killEntities(LocalEntity... entities) { public int killEntities(LocalEntity... entities) {
World world = getWorld(); World world = getWorld();
@ -1267,7 +1266,7 @@ public class BukkitWorld extends LocalWorld {
int amount = 0; int amount = 0;
Set<UUID> toKill = new HashSet<UUID>(); Set<UUID> toKill = new HashSet<UUID>();
for (LocalEntity entity : entities) { for (LocalEntity entity : entities) {
toKill.add(((BukkitEntity) entity).getEntityId()); toKill.add(((com.sk89q.worldedit.bukkit.entity.BukkitEntity) entity).getEntityId());
} }
for (Entity entity : world.getEntities()) { for (Entity entity : world.getEntities()) {
if (toKill.contains(entity.getUniqueId())) { if (toKill.contains(entity.getUniqueId())) {

View File

@ -23,7 +23,10 @@ import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.Extent; 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 com.sk89q.worldedit.util.Location;
import net.minecraft.entity.EntityHanging;
import net.minecraft.entity.EntityList; import net.minecraft.entity.EntityList;
import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagCompound;
@ -40,17 +43,42 @@ class ForgeEntity implements Entity {
@Override @Override
public BaseEntity getState() { public BaseEntity getState() {
String id = EntityList.getEntityString(entity);
if (id != null) {
NBTTagCompound tag = new NBTTagCompound(); NBTTagCompound tag = new NBTTagCompound();
entity.writeToNBT(tag); entity.writeToNBT(tag);
return new BaseEntity(EntityList.getEntityString(entity), NBTConverter.fromNative(tag)); return new BaseEntity(id, NBTConverter.fromNative(tag));
} else {
return null;
}
} }
@Override @Override
public Location getLocation() { public Location getLocation() {
return new Location( Vector position;
ForgeAdapter.adapt(entity.worldObj), float pitch;
new Vector(entity.posX, entity.posY, entity.posZ), float yaw;
ForgeAdapter.adapt(entity.getLookVec()));
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 @Override

View File

@ -32,7 +32,10 @@ import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.blocks.LazyBlock; import com.sk89q.worldedit.blocks.LazyBlock;
import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.internal.helper.MCDirections;
import com.sk89q.worldedit.regions.Region; 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.Location;
import com.sk89q.worldedit.util.TreeGenerator.TreeType; import com.sk89q.worldedit.util.TreeGenerator.TreeType;
import com.sk89q.worldedit.world.AbstractWorld; import com.sk89q.worldedit.world.AbstractWorld;
@ -500,7 +503,29 @@ public class ForgeWorld extends AbstractWorld {
} }
@Override @Override
public List<Entity> getEntities() { @SuppressWarnings("unchecked")
public List<? extends Entity> getEntities(Region region) {
List<Entity> entities = new ArrayList<Entity>();
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<net.minecraft.entity.Entity> 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<? extends Entity> getEntities() {
List<Entity> entities = new ArrayList<Entity>(); List<Entity> entities = new ArrayList<Entity>();
for (Object entity : getWorld().getLoadedEntityList()) { for (Object entity : getWorld().getLoadedEntityList()) {
entities.add(new ForgeEntity((net.minecraft.entity.Entity) entity)); entities.add(new ForgeEntity((net.minecraft.entity.Entity) entity));
@ -518,6 +543,19 @@ public class ForgeWorld extends AbstractWorld {
if (tag != null) { if (tag != null) {
createdEntity.readFromNBT(NBTConverter.toNative(entity.getNbtData())); 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); world.spawnEntityInWorld(createdEntity);
return new ForgeEntity(createdEntity); return new ForgeEntity(createdEntity);
} else { } else {

View File

@ -582,15 +582,10 @@ public class EditSession implements Extent {
return getBlock(position).isAir() && setBlock(position, block); return getBlock(position).isAir() && setBlock(position, block);
} }
@Override
public List<Entity> getEntities() {
return world.getEntities();
}
@Override @Override
@Nullable @Nullable
public Entity createEntity(com.sk89q.worldedit.util.Location location, BaseEntity entity) { 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(); return getWorld().getMaximumPoint();
} }
@Override
public List<? extends Entity> getEntities(Region region) {
return bypassNone.getEntities(region);
}
@Override
public List<? extends Entity> getEntities() {
return bypassNone.getEntities();
}
/** /**
* Finish off the queue. * Finish off the queue.
*/ */
@ -1160,6 +1165,7 @@ public class EditSession implements Extent {
ForwardExtentCopy copy = new ForwardExtentCopy(this, region, buffer, to); ForwardExtentCopy copy = new ForwardExtentCopy(this, region, buffer, to);
copy.setTransform(new AffineTransform().translate(dir.multiply(distance))); copy.setTransform(new AffineTransform().translate(dir.multiply(distance)));
copy.setSourceFunction(remove); // Remove copy.setSourceFunction(remove); // Remove
copy.setRemovingEntities(true);
if (!copyAir) { if (!copyAir) {
copy.setSourceMask(new ExistingBlockMask(this)); copy.setSourceMask(new ExistingBlockMask(this));
} }

View File

@ -652,6 +652,40 @@ public class Vector implements Comparable<Vector> {
throw new RuntimeException("This should not happen"); 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. * Get a block point from a point.
* *
@ -792,4 +826,5 @@ public class Vector implements Comparable<Vector> {
(v1.z + v2.z) / 2 (v1.z + v2.z) / 2
); );
} }
} }

View File

@ -27,6 +27,7 @@ import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.OperationQueue; import com.sk89q.worldedit.function.operation.OperationQueue;
import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.regions.Region;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -82,10 +83,15 @@ public abstract class AbstractDelegateExtent implements Extent {
} }
@Override @Override
public List<Entity> getEntities() { public List<? extends Entity> getEntities() {
return extent.getEntities(); return extent.getEntities();
} }
@Override
public List<? extends Entity> getEntities(Region region) {
return extent.getEntities(region);
}
@Override @Override
public Vector getMinimumPoint() { public Vector getMinimumPoint() {
return extent.getMinimumPoint(); return extent.getMinimumPoint();

View File

@ -22,8 +22,19 @@ package com.sk89q.worldedit.extent;
import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock; 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.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.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; import static com.google.common.base.Preconditions.checkNotNull;
@ -53,4 +64,65 @@ public class ChangeSetExtent extends AbstractDelegateExtent {
return super.setBlock(location, block); 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<? extends Entity> getEntities() {
return wrapEntities(super.getEntities());
}
@Override
public List<? extends Entity> getEntities(Region region) {
return wrapEntities(super.getEntities(region));
}
private List<? extends Entity> wrapEntities(List<? extends Entity> entities) {
List<Entity> newList = new ArrayList<Entity>(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;
}
}
} }

View File

@ -23,6 +23,7 @@ import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.regions.Region;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.List; import java.util.List;
@ -56,6 +57,17 @@ public interface Extent extends InputExtent, OutputExtent {
*/ */
Vector getMaximumPoint(); Vector getMaximumPoint();
/**
* Get a list of all entities within the given region.
* </p>
* 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<? extends Entity> getEntities(Region region);
/** /**
* Get a list of all entities. * Get a list of all entities.
* </p> * </p>
@ -65,7 +77,7 @@ public interface Extent extends InputExtent, OutputExtent {
* *
* @return a list of entities * @return a list of entities
*/ */
List<Entity> getEntities(); List<? extends Entity> getEntities();
/** /**
* Create an entity at the given location. * Create an entity at the given location.

View File

@ -26,6 +26,7 @@ import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.regions.Region;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collections; import java.util.Collections;
@ -49,6 +50,11 @@ public class NullExtent implements Extent {
return nullPoint; return nullPoint;
} }
@Override
public List<Entity> getEntities(Region region) {
return Collections.emptyList();
}
@Override @Override
public List<Entity> getEntities() { public List<Entity> getEntities() {
return Collections.emptyList(); return Collections.emptyList();

View File

@ -31,6 +31,7 @@ import com.sk89q.worldedit.util.Location;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -93,8 +94,19 @@ public class BlockArrayClipboard implements Clipboard {
} }
@Override @Override
public List<Entity> getEntities() { public List<? extends Entity> getEntities(Region region) {
return new ArrayList<Entity>(entities); List<Entity> filtered = new ArrayList<Entity>();
for (Entity entity : entities) {
if (region.contains(entity.getLocation().toVector())) {
filtered.add(entity);
}
}
return Collections.unmodifiableList(filtered);
}
@Override
public List<? extends Entity> getEntities() {
return Collections.unmodifiableList(entities);
} }
@Nullable @Nullable

View File

@ -0,0 +1,103 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.function.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;
}
}
}

View File

@ -19,20 +19,25 @@
package com.sk89q.worldedit.function.operation; package com.sk89q.worldedit.function.operation;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException; 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.CombinedRegionFunction;
import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.RegionMaskingFilter; import com.sk89q.worldedit.function.RegionMaskingFilter;
import com.sk89q.worldedit.function.block.ExtentBlockCopy; 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.Mask;
import com.sk89q.worldedit.function.mask.Masks; import com.sk89q.worldedit.function.mask.Masks;
import com.sk89q.worldedit.function.visitor.EntityVisitor;
import com.sk89q.worldedit.function.visitor.RegionVisitor; import com.sk89q.worldedit.function.visitor.RegionVisitor;
import com.sk89q.worldedit.math.transform.Identity; import com.sk89q.worldedit.math.transform.Identity;
import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.math.transform.Transform;
import com.sk89q.worldedit.regions.Region; 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -52,6 +57,7 @@ public class ForwardExtentCopy implements Operation {
private final Vector to; private final Vector to;
private int repetitions = 1; private int repetitions = 1;
private Mask sourceMask = Masks.alwaysTrue(); private Mask sourceMask = Masks.alwaysTrue();
private boolean removingEntities;
private RegionFunction sourceFunction = null; private RegionFunction sourceFunction = null;
private Transform transform = new Identity(); private Transform transform = new Identity();
private Transform currentTransform = null; private Transform currentTransform = null;
@ -177,6 +183,24 @@ public class ForwardExtentCopy implements Operation {
this.repetitions = repetitions; 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. * Get the number of affected objects.
* *
@ -200,13 +224,19 @@ public class ForwardExtentCopy implements Operation {
currentTransform = transform; currentTransform = transform;
} }
ExtentBlockCopy copy = new ExtentBlockCopy(source, from, destination, to, currentTransform); ExtentBlockCopy blockCopy = new ExtentBlockCopy(source, from, destination, to, currentTransform);
RegionMaskingFilter filter = new RegionMaskingFilter(sourceMask, copy); RegionMaskingFilter filter = new RegionMaskingFilter(sourceMask, blockCopy);
RegionFunction function = sourceFunction != null ? new CombinedRegionFunction(filter, sourceFunction) : filter; RegionFunction function = sourceFunction != null ? new CombinedRegionFunction(filter, sourceFunction) : filter;
RegionVisitor visitor = new RegionVisitor(region, function); RegionVisitor blockVisitor = new RegionVisitor(region, function);
lastVisitor = visitor;
ExtentEntityCopy entityCopy = new ExtentEntityCopy(from, destination, to, currentTransform);
entityCopy.setRemoving(removingEntities);
List<? extends Entity> entities = source.getEntities(region);
EntityVisitor entityVisitor = new EntityVisitor(entities.iterator(), entityCopy);
lastVisitor = blockVisitor;
currentTransform = currentTransform.combine(transform); currentTransform = currentTransform.combine(transform);
return new DelegateOperation(this, visitor); return new DelegateOperation(this, new OperationQueue(blockVisitor, entityVisitor));
} else { } else {
return null; return null;
} }

View File

@ -34,7 +34,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/ */
public class EntityVisitor implements Operation { public class EntityVisitor implements Operation {
private final Iterator<Entity> iterator; private final Iterator<? extends Entity> iterator;
private final EntityFunction function; private final EntityFunction function;
private int affected = 0; private int affected = 0;
@ -44,7 +44,7 @@ public class EntityVisitor implements Operation {
* @param iterator the iterator * @param iterator the iterator
* @param function the function * @param function the function
*/ */
public EntityVisitor(Iterator<Entity> iterator, EntityFunction function) { public EntityVisitor(Iterator<? extends Entity> iterator, EntityFunction function) {
checkNotNull(iterator); checkNotNull(iterator);
checkNotNull(function); checkNotNull(function);
this.iterator = iterator; this.iterator = iterator;

View File

@ -0,0 +1,68 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.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);
}
}

View File

@ -0,0 +1,65 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.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;
}
}
}

View File

@ -171,11 +171,6 @@ public class LocalWorldAdapter extends LocalWorld {
world.simulateBlockMine(position); world.simulateBlockMine(position);
} }
@Override
public LocalEntity[] getEntities(Region region) {
return world.getEntities(region);
}
@Override @Override
public int killEntities(LocalEntity... entity) { public int killEntities(LocalEntity... entity) {
return world.killEntities(entity); return world.killEntities(entity);
@ -293,6 +288,11 @@ public class LocalWorldAdapter extends LocalWorld {
return world.getMaximumPoint(); return world.getMaximumPoint();
} }
@Override
public List<? extends Entity> getEntities(Region region) {
return world.getEntities(region);
}
@Override @Override
public BaseBlock getBlock(Vector position) { public BaseBlock getBlock(Vector position) {
return world.getBlock(position); return world.getBlock(position);
@ -335,7 +335,7 @@ public class LocalWorldAdapter extends LocalWorld {
} }
@Override @Override
public List<Entity> getEntities() { public List<? extends Entity> getEntities() {
return world.getEntities(); return world.getEntities();
} }

View File

@ -0,0 +1,62 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.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;
}
}
}

View File

@ -0,0 +1,159 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.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).
*
* <p>This evaluates as false for directions that have a non-zero
* Y-component.</p>
*
* @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() {
}
}
}

View File

@ -113,8 +113,7 @@ public class Location {
* @param direction the direction vector * @param direction the direction vector
*/ */
public Location(Extent extent, Vector position, Vector direction) { public Location(Extent extent, Vector position, Vector direction) {
this(extent, position, 0, 0); this(extent, position, direction.toYaw(), direction.toPitch());
this.setDirection(direction);
} }
/** /**
@ -194,24 +193,7 @@ public class Location {
* @return the new instance * @return the new instance
*/ */
public Location setDirection(Vector direction) { public Location setDirection(Vector direction) {
double x = direction.getX(); return new Location(extent, position, direction.toYaw(), direction.toPitch());
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);
}
} }
/** /**

View File

@ -19,8 +19,13 @@
package com.sk89q.worldedit.world; 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.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.BaseBlock;
import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.blocks.BlockID; 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.BlockMask;
import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.TreeGenerator.TreeType; import com.sk89q.worldedit.util.TreeGenerator.TreeType;
import javax.annotation.Nullable; 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 @Override
public int killEntities(LocalEntity... entities) { public int killEntities(LocalEntity... entities) {
return 0; return 0;

View File

@ -109,6 +109,11 @@ public class NullWorld extends AbstractWorld {
return new BaseBlock(BlockID.AIR); return new BaseBlock(BlockID.AIR);
} }
@Override
public List<Entity> getEntities(Region region) {
return Collections.emptyList();
}
@Override @Override
public List<Entity> getEntities() { public List<Entity> getEntities() {
return Collections.emptyList(); return Collections.emptyList();

View File

@ -209,14 +209,6 @@ public interface World extends Extent {
*/ */
void simulateBlockMine(Vector position); 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. * Kill the entities listed in the provided array.
* *

View File

@ -51,25 +51,6 @@ public class LocationTest {
assertEquals(world2, location2.getExtent()); 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 @Test
public void testToVector() throws Exception { public void testToVector() throws Exception {
World world = mock(World.class); World world = mock(World.class);