Re-implement full Entity support in history, entity removal, entity creation, etc. (#1812)

Co-authored-by: Alexander Brandes <mc.cache@web.de>
This commit is contained in:
Jordan
2022-06-21 14:52:02 +01:00
committed by GitHub
parent d62c88a2ca
commit 968799503f
43 changed files with 556 additions and 336 deletions

View File

@ -621,6 +621,15 @@ public class Settings extends Config {
})
public boolean KEEP_ENTITIES_IN_BLOCKS = false;
@Comment({
"[SAFE] Attempt to remove entities from the world if they were not present in the expected chunk (default: true)",
" - Sometimes an entity may have moved into a different chunk to that which FAWE expected",
" - This option allows FAWE to attempt to remove the entity, even if present in a different chunk",
" - If the entity is in an unloaded or partially loaded chunk, this will fail",
" - If an entity cannot be removed, it is possible duplicate entities may be created when using undo and/or redo"
})
public boolean REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL = true;
@Comment({
"Other experimental features"
})

View File

@ -22,6 +22,7 @@ import com.sk89q.worldedit.world.block.BlockTypes;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.UUID;
public abstract class FaweRegionExtent extends ResettableExtent implements IBatchProcessor {
@ -149,6 +150,18 @@ public abstract class FaweRegionExtent extends ResettableExtent implements IBatc
return super.createEntity(location, entity);
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity, UUID uuid) {
if (!contains(location.getBlockX(), location.getBlockY(), location.getBlockZ())) {
if (!limit.MAX_FAILS()) {
WEManager.weManager().cancelEditSafe(this, FaweCache.OUTSIDE_REGION);
}
return null;
}
return super.createEntity(location, entity, uuid);
}
@Override
public ProcessorScope getScope() {
return ProcessorScope.READING_SET_BLOCKS;

View File

@ -18,6 +18,7 @@ import com.sk89q.worldedit.world.block.BlockStateHolder;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static com.google.common.base.Preconditions.checkNotNull;
@ -78,6 +79,16 @@ public class HistoryExtent extends AbstractDelegateExtent {
return entity;
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity state, UUID uuid) {
final Entity entity = super.createEntity(location, state, uuid);
if (state != null) {
this.changeSet.addEntityCreate(state.getNbtData());
}
return entity;
}
@Override
public List<? extends Entity> getEntities() {
return this.wrapEntities(super.getEntities());

View File

@ -90,6 +90,21 @@ public class LimitExtent extends AbstractDelegateExtent {
}
}
@Override
@Nullable
public Entity createEntity(Location location, BaseEntity entity, UUID uuid) {
limit.THROW_MAX_CHANGES();
limit.THROW_MAX_ENTITIES();
try {
return super.createEntity(location, entity, uuid);
} catch (FaweException e) {
if (!limit.MAX_FAILS()) {
throw e;
}
return null;
}
}
@Override
public void removeEntity(int x, int y, int z, UUID uuid) {
limit.THROW_MAX_CHANGES();

View File

@ -36,6 +36,7 @@ import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Future;
//todo This should be removed in favor of com.sk89q.worldedit.extent.NullExtent
@ -87,6 +88,12 @@ public class NullExtent extends FaweRegionExtent implements IBatchProcessor {
throw reason;
}
@Nullable
@Override
public Entity createEntity(Location arg0, BaseEntity arg1, UUID arg2) {
throw reason;
}
@Override
public BlockState getBlock(BlockVector3 position) {
throw reason;

View File

@ -16,6 +16,8 @@ import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockTypes;
import java.util.UUID;
public class ProcessedWEExtent extends AbstractDelegateExtent {
private final FaweLimit limit;
@ -43,6 +45,18 @@ public class ProcessedWEExtent extends AbstractDelegateExtent {
return super.createEntity(location, entity);
}
@Override
public Entity createEntity(Location location, BaseEntity entity, UUID uuid) {
if (entity == null) {
return null;
}
if (!limit.MAX_ENTITIES()) {
WEManager.weManager().cancelEditSafe(this, FaweCache.MAX_ENTITIES);
return null;
}
return super.createEntity(location, entity, uuid);
}
@Override
public BlockState getBlock(int x, int y, int z) {
if (!limit.MAX_CHECKS()) {

View File

@ -28,6 +28,7 @@ import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
@ -65,6 +66,12 @@ public class StripNBTExtent extends AbstractDelegateExtent implements IBatchProc
return super.createEntity(location, stripEntityNBT(entity));
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity, UUID uuid) {
return super.createEntity(location, stripEntityNBT(entity), uuid);
}
@SuppressWarnings("unchecked")
public <B extends BlockStateHolder<B>> B stripBlockNBT(B block) {
if (!(block instanceof BaseBlock localBlock)) {

View File

@ -5,23 +5,16 @@ import com.fastasyncworldedit.core.math.IntTriple;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CPUOptimizedClipboard extends LinearClipboard {
@ -209,22 +202,4 @@ public class CPUOptimizedClipboard extends LinearClipboard {
return true;
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity) {
BlockArrayClipboard.ClipboardEntity ret = new BlockArrayClipboard.ClipboardEntity(location, entity);
entities.add(ret);
return ret;
}
@Override
public List<? extends Entity> getEntities() {
return new ArrayList<>(entities);
}
@Override
public void removeEntity(Entity entity) {
this.entities.remove(entity);
}
}

View File

@ -16,12 +16,10 @@ import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.NBTOutputStream;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BaseBlock;
@ -30,7 +28,6 @@ import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockTypes;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
@ -48,7 +45,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* A clipboard with disk backed storage. (lower memory + loads on crash)
@ -714,47 +710,4 @@ public class DiskOptimizedClipboard extends LinearClipboard {
return false;
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity) {
BlockArrayClipboard.ClipboardEntity ret = new BlockArrayClipboard.ClipboardEntity(location, entity);
entities.add(ret);
return ret;
}
@Override
public List<? extends Entity> getEntities() {
return new ArrayList<>(entities);
}
@Override
public List<? extends Entity> getEntities(Region region) {
return entities
.stream()
.filter(e -> region.contains(e.getLocation().toBlockPoint())).collect(Collectors.toList());
}
@Override
public void removeEntity(Entity entity) {
if (!(entity instanceof BlockArrayClipboard.ClipboardEntity)) {
Location loc = entity.getLocation();
removeEntity(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), entity.getState().getNbtData().getUUID());
} else {
this.entities.remove(entity);
}
}
@Override
public void removeEntity(int x, int y, int z, UUID uuid) {
Iterator<BlockArrayClipboard.ClipboardEntity> iter = this.entities.iterator();
while (iter.hasNext()) {
BlockArrayClipboard.ClipboardEntity entity = iter.next();
UUID entUUID = entity.getState().getNbtData().getUUID();
if (uuid.equals(entUUID)) {
iter.remove();
return;
}
}
}
}

View File

@ -5,21 +5,33 @@ import com.fastasyncworldedit.core.function.visitor.Order;
import com.fastasyncworldedit.core.jnbt.streamer.IntValueReader;
import com.google.common.collect.ForwardingIterator;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.NBTUtils;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard.ClipboardEntity;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
public abstract class LinearClipboard extends SimpleClipboard {
@ -83,6 +95,25 @@ public abstract class LinearClipboard extends SimpleClipboard {
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity) {
BlockArrayClipboard.ClipboardEntity ret = new BlockArrayClipboard.ClipboardEntity(location, entity);
entities.add(ret);
return ret;
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity, UUID uuid) {
Map<String, Tag> map = new HashMap<>(entity.getNbtData().getValue());
NBTUtils.addUUIDToMap(map, uuid);
entity.setNbtData(new CompoundTag(map));
BlockArrayClipboard.ClipboardEntity ret = new BlockArrayClipboard.ClipboardEntity(location, entity);
entities.add(ret);
return ret;
}
@Override
public void removeEntity(int x, int y, int z, UUID uuid) {
Iterator<ClipboardEntity> iter = this.entities.iterator();
@ -96,6 +127,28 @@ public abstract class LinearClipboard extends SimpleClipboard {
}
}
@Override
public List<? extends Entity> getEntities() {
return new ArrayList<>(entities);
}
@Override
public void removeEntity(Entity entity) {
if (!(entity instanceof BlockArrayClipboard.ClipboardEntity)) {
Location loc = entity.getLocation();
removeEntity(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), entity.getState().getNbtData().getUUID());
} else {
this.entities.remove(entity);
}
}
@Override
public List<? extends Entity> getEntities(Region region) {
return entities
.stream()
.filter(e -> region.contains(e.getLocation().toBlockPoint())).collect(Collectors.toList());
}
private class LinearFilter extends AbstractFilterBlock {
private int index = -1;

View File

@ -7,13 +7,8 @@ import com.fastasyncworldedit.core.util.MainUtil;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard.ClipboardEntity;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BaseBlock;
@ -22,14 +17,10 @@ import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
import com.sk89q.worldedit.world.block.BlockTypes;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class MemoryOptimizedClipboard extends LinearClipboard {
@ -293,31 +284,4 @@ public class MemoryOptimizedClipboard extends LinearClipboard {
return true;
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity) {
BlockArrayClipboard.ClipboardEntity ret = new BlockArrayClipboard.ClipboardEntity(location, entity);
entities.add(ret);
return ret;
}
@Override
public List<? extends Entity> getEntities() {
return new ArrayList<>(entities);
}
@Override
public List<? extends Entity> getEntities(Region region) {
return entities
.stream()
.filter(e -> region.contains(e.getLocation().toBlockPoint())).collect(Collectors.toList());
}
@Override
public void removeEntity(Entity entity) {
if (entity instanceof ClipboardEntity) {
this.entities.remove(entity);
}
}
}

View File

@ -2,10 +2,13 @@ package com.fastasyncworldedit.core.extent.clipboard;
import com.fastasyncworldedit.core.Fawe;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.NBTUtils;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.session.request.Request;
@ -14,7 +17,11 @@ import com.sk89q.worldedit.world.World;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
public abstract class ReadOnlyClipboard extends SimpleClipboard {
@ -103,6 +110,12 @@ public abstract class ReadOnlyClipboard extends SimpleClipboard {
throw new UnsupportedOperationException("Clipboard is immutable");
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity, UUID uuid) {
throw new UnsupportedOperationException("Clipboard is immutable");
}
@Override
public void removeEntity(Entity entity) {
throw new UnsupportedOperationException("Clipboard is immutable");

View File

@ -12,6 +12,7 @@ import com.sk89q.worldedit.world.block.BlockStateHolder;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.UUID;
public class MultiTransform extends RandomTransform {
@ -90,4 +91,14 @@ public class MultiTransform extends RandomTransform {
return created;
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity, UUID uuid) {
Entity created = null;
for (AbstractDelegateExtent extent : extents) {
created = extent.createEntity(location, entity, uuid);
}
return created;
}
}

View File

@ -13,6 +13,7 @@ import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import javax.annotation.Nullable;
import java.util.UUID;
public class ScaleTransform extends ResettableExtent {
@ -181,4 +182,17 @@ public class ScaleTransform extends ResettableExtent {
return super.createEntity(newLoc, entity);
}
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity, UUID uuid) {
Location newLoc = new Location(location.getExtent(),
getPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()),
location.getYaw(), location.getPitch()
);
if (!getExtent().contains(newLoc.toBlockPoint())) {
return null;
}
return super.createEntity(newLoc, entity, uuid);
}
}

View File

@ -14,6 +14,7 @@ import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import javax.annotation.Nullable;
import java.util.UUID;
public abstract class SelectTransform extends ResettableExtent {
@ -52,6 +53,13 @@ public abstract class SelectTransform extends ResettableExtent {
.createEntity(position, entity);
}
@Nullable
@Override
public Entity createEntity(Location position, BaseEntity entity, UUID uuid) {
return getExtent(position.getBlockX(), position.getBlockY(), position.getBlockZ())
.createEntity(position, entity, uuid);
}
@Override
public boolean setBiome(BlockVector3 position, BiomeType biome) {
return getExtent(position).setBiome(position, biome);

View File

@ -53,15 +53,8 @@ public class MutableEntityChange implements Change {
@SuppressWarnings({"unchecked"})
public void delete(UndoContext context) {
Map<String, Tag> map = tag.getValue();
long most;
long least;
if (map.containsKey("UUIDMost")) {
most = ((LongTag) map.get("UUIDMost")).getValue();
least = ((LongTag) map.get("UUIDLeast")).getValue();
} else if (map.containsKey("PersistentIDMSB")) {
most = ((LongTag) map.get("PersistentIDMSB")).getValue();
least = ((LongTag) map.get("PersistentIDLSB")).getValue();
} else {
UUID uuid = tag.getUUID();
if (uuid == null) {
LOGGER.info("Skipping entity without uuid.");
return;
}
@ -69,7 +62,6 @@ public class MutableEntityChange implements Change {
int x = MathMan.roundInt(pos.get(0).getValue());
int y = MathMan.roundInt(pos.get(1).getValue());
int z = MathMan.roundInt(pos.get(2).getValue());
UUID uuid = new UUID(most, least);
context.getExtent().removeEntity(x, y, z, uuid);
}
@ -89,7 +81,7 @@ public class MutableEntityChange implements Change {
String id = tag.getString("Id");
EntityType type = EntityTypes.parse(id);
BaseEntity entity = new BaseEntity(type, tag);
context.getExtent().createEntity(location, entity);
extent.createEntity(location, entity, tag.getUUID());
}
}

View File

@ -5,6 +5,7 @@ import com.sk89q.jnbt.DoubleTag;
import com.sk89q.jnbt.IntArrayTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.LongTag;
import com.sk89q.jnbt.NBTUtils;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.WorldEditException;
@ -119,6 +120,11 @@ public interface IChunkExtent<T extends IChunk> extends Extent {
@Override
default Entity createEntity(Location location, BaseEntity entity) {
return createEntity(location, entity, UUID.randomUUID());
}
@Override
default Entity createEntity(Location location, BaseEntity entity, UUID uuid) {
final IChunk chunk = getOrCreateChunk(location.getBlockX() >> 4, location.getBlockZ() >> 4);
Map<String, Tag> map = new HashMap<>(entity.getNbtData().getValue()); //do not modify original entity data
map.put("Id", new StringTag(entity.getType().getName()));
@ -130,23 +136,10 @@ public interface IChunkExtent<T extends IChunk> extends Extent {
posList.add(new DoubleTag(location.getZ()));
map.put("Pos", new ListTag(DoubleTag.class, posList));
//set new uuid
UUID newuuid = UUID.randomUUID();
int[] uuidArray = new int[4];
uuidArray[0] = (int) (newuuid.getMostSignificantBits() >> 32);
uuidArray[1] = (int) newuuid.getMostSignificantBits();
uuidArray[2] = (int) (newuuid.getLeastSignificantBits() >> 32);
uuidArray[3] = (int) newuuid.getLeastSignificantBits();
map.put("UUID", new IntArrayTag(uuidArray));
map.put("UUIDMost", new LongTag(newuuid.getMostSignificantBits()));
map.put("UUIDLeast", new LongTag(newuuid.getLeastSignificantBits()));
map.put("PersistentIDMSB", new LongTag(newuuid.getMostSignificantBits()));
map.put("PersistentIDLSB", new LongTag(newuuid.getLeastSignificantBits()));
NBTUtils.addUUIDToMap(map, uuid);
chunk.setEntity(new CompoundTag(map));
return new IChunkEntity(this, location, newuuid, entity);
return new IChunkEntity(this, location, uuid, entity);
}
@Override

View File

@ -1056,14 +1056,17 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
IChunkGet get = getOrCreateGet();
boolean postProcess = !(getExtent().getPostProcessor() instanceof EmptyBatchProcessor);
get.setCreateCopy(postProcess);
set = getExtent().processSet(this, get, set);
try {
return get.call(set, finalize);
} finally {
if (postProcess) {
getExtent().postProcess(this, get.getCopy(), set);
}
final IChunkSet iChunkSet = getExtent().processSet(this, get, set);
Runnable finalizer;
if (postProcess) {
finalizer = () -> {
getExtent().postProcess(this, get.getCopy(), iChunkSet);
finalize.run();
};
} else {
finalizer = finalize;
}
return get.call(set, finalizer);
}
return null;
}

View File

@ -42,6 +42,7 @@ import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public class WorldWrapper extends AbstractWorld {
@ -326,6 +327,12 @@ public class WorldWrapper extends AbstractWorld {
return parent.createEntity(location, entity);
}
@Override
@Nullable
public Entity createEntity(Location location, BaseEntity entity, UUID uuid) {
return parent.createEntity(location, entity, uuid);
}
@Override
public BlockState getBlock(BlockVector3 position) {
return parent.getBlock(position);