Fix passenger entity saving and loading (#2058)

* Fix incorrect saving and subsequent loading of passenger entities in vehicles.

Originally from https://github.com/EngineHub/WorldEdit/issues/1763#issuecomment-1062429891.

Co-authored-by: wizjany <wizjany@gmail.com>

* Port to Fabric/Forge.

Rewrite in Sponge to use EntityArchetype.
Sponge is untested because WE doesn't properly build/run on it right now apparently?

* NBT Constants.

Co-authored-by: Red_Epicness <red.epicness@icloud.com>
This commit is contained in:
wizjany 2022-03-18 00:33:35 -04:00 committed by GitHub
parent 356cd2c8b2
commit e715ccc3b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 233 additions and 126 deletions

View File

@ -421,6 +421,11 @@ public BaseEntity getEntity(org.bukkit.entity.Entity entity) {
CraftEntity craftEntity = ((CraftEntity) entity);
Entity mcEntity = craftEntity.getHandle();
// Do not allow creating of passenger entity snapshots, passengers are included in the vehicle entity
if (mcEntity.isPassenger()) {
return null;
}
String id = getEntityId(mcEntity);
net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag();
@ -437,27 +442,48 @@ public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
ServerLevel worldServer = craftWorld.getHandle();
Entity createdEntity = createEntityFromId(state.getType().getId(), craftWorld.getHandle());
String entityId = state.getType().getId();
CompoundTag nativeTag = state.getNbtData();
net.minecraft.nbt.CompoundTag tag;
if (nativeTag != null) {
tag = (net.minecraft.nbt.CompoundTag) fromNative(nativeTag);
removeUnwantedEntityTagsRecursively(tag);
} else {
tag = new net.minecraft.nbt.CompoundTag();
}
tag.putString("id", entityId);
Entity createdEntity = EntityType.loadEntityRecursive(tag, craftWorld.getHandle(), (loadedEntity) -> {
loadedEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
return loadedEntity;
});
if (createdEntity != null) {
CompoundTag nativeTag = state.getNbtData();
if (nativeTag != null) {
net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) fromNative(nativeTag);
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.remove(name);
}
readTagIntoEntity(tag, createdEntity);
}
createdEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
worldServer.addEntity(createdEntity, SpawnReason.CUSTOM);
worldServer.addAllEntities(createdEntity, SpawnReason.CUSTOM);
return createdEntity.getBukkitEntity();
} else {
return null;
}
}
// This removes all unwanted tags from the main entity and all its passengers
private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) {
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.remove(name);
}
// Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive
if (tag.contains("Passengers", NBTConstants.TYPE_LIST)) {
net.minecraft.nbt.ListTag nbttaglist = tag.getList("Passengers", NBTConstants.TYPE_COMPOUND);
for (int i = 0; i < nbttaglist.size(); ++i) {
removeUnwantedEntityTagsRecursively(nbttaglist.getCompound(i));
}
}
}
@Override
public Component getRichBlockName(BlockType blockType) {
return TranslatableComponent.of(getBlockFromType(blockType).getDescriptionId());

View File

@ -412,6 +412,11 @@ public BaseEntity getEntity(org.bukkit.entity.Entity entity) {
CraftEntity craftEntity = ((CraftEntity) entity);
Entity mcEntity = craftEntity.getHandle();
// Do not allow creating of passenger entity snapshots, passengers are included in the vehicle entity
if (mcEntity.isPassenger()) {
return null;
}
String id = getEntityId(mcEntity);
net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag();
@ -428,27 +433,48 @@ public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
ServerLevel worldServer = craftWorld.getHandle();
Entity createdEntity = createEntityFromId(state.getType().getId(), craftWorld.getHandle());
String entityId = state.getType().getId();
CompoundTag nativeTag = state.getNbtData();
net.minecraft.nbt.CompoundTag tag;
if (nativeTag != null) {
tag = (net.minecraft.nbt.CompoundTag) fromNative(nativeTag);
removeUnwantedEntityTagsRecursively(tag);
} else {
tag = new net.minecraft.nbt.CompoundTag();
}
tag.putString("id", entityId);
Entity createdEntity = EntityType.loadEntityRecursive(tag, craftWorld.getHandle(), (loadedEntity) -> {
loadedEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
return loadedEntity;
});
if (createdEntity != null) {
CompoundTag nativeTag = state.getNbtData();
if (nativeTag != null) {
net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) fromNative(nativeTag);
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.remove(name);
}
readTagIntoEntity(tag, createdEntity);
}
createdEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
worldServer.addFreshEntity(createdEntity, SpawnReason.CUSTOM);
worldServer.addFreshEntityWithPassengers(createdEntity, SpawnReason.CUSTOM);
return createdEntity.getBukkitEntity();
} else {
return null;
}
}
// This removes all unwanted tags from the main entity and all its passengers
private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) {
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.remove(name);
}
// Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive
if (tag.contains("Passengers", NBTConstants.TYPE_LIST)) {
net.minecraft.nbt.ListTag nbttaglist = tag.getList("Passengers", NBTConstants.TYPE_COMPOUND);
for (int i = 0; i < nbttaglist.size(); ++i) {
removeUnwantedEntityTagsRecursively(nbttaglist.getCompound(i));
}
}
}
@Override
public Component getRichBlockName(BlockType blockType) {
return TranslatableComponent.of(getBlockFromType(blockType).getDescriptionId());

View File

@ -412,6 +412,11 @@ public BaseEntity getEntity(org.bukkit.entity.Entity entity) {
CraftEntity craftEntity = ((CraftEntity) entity);
Entity mcEntity = craftEntity.getHandle();
// Do not allow creating of passenger entity snapshots, passengers are included in the vehicle entity
if (mcEntity.isPassenger()) {
return null;
}
String id = getEntityId(mcEntity);
net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag();
@ -428,27 +433,48 @@ public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state
CraftWorld craftWorld = ((CraftWorld) location.getWorld());
ServerLevel worldServer = craftWorld.getHandle();
Entity createdEntity = createEntityFromId(state.getType().getId(), craftWorld.getHandle());
String entityId = state.getType().getId();
CompoundTag nativeTag = state.getNbtData();
net.minecraft.nbt.CompoundTag tag;
if (nativeTag != null) {
tag = (net.minecraft.nbt.CompoundTag) fromNative(nativeTag);
removeUnwantedEntityTagsRecursively(tag);
} else {
tag = new net.minecraft.nbt.CompoundTag();
}
tag.putString("id", entityId);
Entity createdEntity = EntityType.loadEntityRecursive(tag, craftWorld.getHandle(), (loadedEntity) -> {
loadedEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
return loadedEntity;
});
if (createdEntity != null) {
CompoundTag nativeTag = state.getNbtData();
if (nativeTag != null) {
net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) fromNative(nativeTag);
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.remove(name);
}
readTagIntoEntity(tag, createdEntity);
}
createdEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
worldServer.addFreshEntity(createdEntity, SpawnReason.CUSTOM);
worldServer.addFreshEntityWithPassengers(createdEntity, SpawnReason.CUSTOM);
return createdEntity.getBukkitEntity();
} else {
return null;
}
}
// This removes all unwanted tags from the main entity and all its passengers
private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) {
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.remove(name);
}
// Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive
if (tag.contains("Passengers", NBTConstants.TYPE_LIST)) {
net.minecraft.nbt.ListTag nbttaglist = tag.getList("Passengers", NBTConstants.TYPE_COMPOUND);
for (int i = 0; i < nbttaglist.size(); ++i) {
removeUnwantedEntityTagsRecursively(nbttaglist.getCompound(i));
}
}
}
@Override
public Component getRichBlockName(BlockType blockType) {
return TranslatableComponent.of(getBlockFromType(blockType).getDescriptionId());

View File

@ -78,7 +78,7 @@ public BaseEntity(BaseEntity other) {
@Override
public boolean hasNbtData() {
return true;
return nbtData != null;
}
@Nullable

View File

@ -49,14 +49,13 @@ class FabricEntity implements Entity {
@Override
public BaseEntity getState() {
net.minecraft.world.entity.Entity entity = entityRef.get();
if (entity != null) {
ResourceLocation id = Registry.ENTITY_TYPE.getKey(entity.getType());
CompoundTag tag = new CompoundTag();
entity.saveWithoutId(tag);
return new BaseEntity(EntityTypes.get(id.toString()), NBTConverter.fromNative(tag));
} else {
if (entity == null || entity.isPassenger()) {
return null;
}
ResourceLocation id = Registry.ENTITY_TYPE.getKey(entity.getType());
CompoundTag tag = new CompoundTag();
entity.saveWithoutId(tag);
return new BaseEntity(EntityTypes.get(id.toString()), NBTConverter.fromNative(tag));
}
@Override

View File

@ -27,6 +27,7 @@
import com.google.common.collect.Streams;
import com.google.common.util.concurrent.Futures;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.NBTConstants;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseItem;
@ -629,28 +630,45 @@ public List<? extends Entity> getEntities() {
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity) {
Level world = getWorld();
final Optional<EntityType<?>> entityType = EntityType.byString(entity.getType().getId());
ServerLevel world = (ServerLevel) getWorld();
String entityId = entity.getType().getId();
final Optional<EntityType<?>> entityType = EntityType.byString(entityId);
if (entityType.isEmpty()) {
return null;
}
net.minecraft.world.entity.Entity createdEntity = entityType.get().create(world);
if (createdEntity != null) {
CompoundTag nativeTag = entity.getNbtData();
if (nativeTag != null) {
net.minecraft.nbt.CompoundTag tag = NBTConverter.toNative(entity.getNbtData());
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.remove(name);
}
createdEntity.load(tag);
}
createdEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
world.addFreshEntity(createdEntity);
return new FabricEntity(createdEntity);
CompoundTag nativeTag = entity.getNbtData();
net.minecraft.nbt.CompoundTag tag;
if (nativeTag != null) {
tag = NBTConverter.toNative(entity.getNbtData());
removeUnwantedEntityTagsRecursively(tag);
} else {
return null;
tag = new net.minecraft.nbt.CompoundTag();
}
tag.putString("id", entityId);
net.minecraft.world.entity.Entity createdEntity = EntityType.loadEntityRecursive(tag, world, (loadedEntity) -> {
loadedEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
return loadedEntity;
});
if (createdEntity != null) {
world.addFreshEntityWithPassengers(createdEntity);
return new FabricEntity(createdEntity);
}
return null;
}
private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) {
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.remove(name);
}
// Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive
if (tag.contains("Passengers", NBTConstants.TYPE_LIST)) {
net.minecraft.nbt.ListTag nbttaglist = tag.getList("Passengers", NBTConstants.TYPE_COMPOUND);
for (int i = 0; i < nbttaglist.size(); ++i) {
removeUnwantedEntityTagsRecursively(nbttaglist.getCompound(i));
}
}
}

View File

@ -49,18 +49,16 @@ class ForgeEntity implements Entity {
@Override
public BaseEntity getState() {
net.minecraft.world.entity.Entity entity = entityRef.get();
if (entity != null) {
ResourceLocation id = entity.getType().getRegistryName();
if (id != null) {
CompoundTag tag = new CompoundTag();
entity.saveWithoutId(tag);
return new BaseEntity(EntityTypes.get(id.toString()), NBTConverter.fromNative(tag));
} else {
return null;
}
} else {
if (entity == null || entity.isPassenger()) {
return null;
}
ResourceLocation id = entity.getType().getRegistryName();
if (id == null) {
return null;
}
CompoundTag tag = new CompoundTag();
entity.saveWithoutId(tag);
return new BaseEntity(EntityTypes.get(id.toString()), NBTConverter.fromNative(tag));
}
@Override

View File

@ -27,6 +27,7 @@
import com.google.common.collect.Streams;
import com.google.common.util.concurrent.Futures;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.NBTConstants;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseItem;
@ -611,27 +612,44 @@ public List<? extends Entity> getEntities() {
@Override
public Entity createEntity(Location location, BaseEntity entity) {
ServerLevel world = getWorld();
final Optional<EntityType<?>> entityType = EntityType.byString(entity.getType().getId());
String entityId = entity.getType().getId();
final Optional<EntityType<?>> entityType = EntityType.byString(entityId);
if (entityType.isEmpty()) {
return null;
}
net.minecraft.world.entity.Entity createdEntity = entityType.get().create(world);
if (createdEntity != null) {
CompoundTag nativeTag = entity.getNbtData();
if (nativeTag != null) {
net.minecraft.nbt.CompoundTag tag = NBTConverter.toNative(entity.getNbtData());
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.remove(name);
}
createdEntity.load(tag);
}
createdEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
world.addFreshEntity(createdEntity);
return new ForgeEntity(createdEntity);
CompoundTag nativeTag = entity.getNbtData();
net.minecraft.nbt.CompoundTag tag;
if (nativeTag != null) {
tag = NBTConverter.toNative(entity.getNbtData());
removeUnwantedEntityTagsRecursively(tag);
} else {
return null;
tag = new net.minecraft.nbt.CompoundTag();
}
tag.putString("id", entityId);
net.minecraft.world.entity.Entity createdEntity = EntityType.loadEntityRecursive(tag, world, (loadedEntity) -> {
loadedEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
return loadedEntity;
});
if (createdEntity != null) {
world.addFreshEntityWithPassengers(createdEntity);
return new ForgeEntity(createdEntity);
}
return null;
}
private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) {
for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
tag.remove(name);
}
// Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive
if (tag.contains("Passengers", NBTConstants.TYPE_LIST)) {
net.minecraft.nbt.ListTag nbttaglist = tag.getList("Passengers", NBTConstants.TYPE_COMPOUND);
for (int i = 0; i < nbttaglist.size(); ++i) {
removeUnwantedEntityTagsRecursively(nbttaglist.getCompound(i));
}
}
}

View File

@ -54,7 +54,7 @@
because("Sponge 8 (will?) provides Log4J")
})
api("org.apache.logging.log4j:log4j-api")
api("org.bstats:bstats-sponge:3.0.0")
implementation("org.bstats:bstats-sponge:3.0.0")
testImplementation("org.mockito:mockito-core:${Versions.MOCKITO}")
}
@ -71,7 +71,7 @@
}
include(dependency(":worldedit-core"))
relocate("org.antlr.v4", "com.sk89q.worldedit.sponge.antlr4")
relocate("org.antlr.v4", "com.sk89q.worldedit.antlr4")
include(dependency("org.antlr:antlr4-runtime"))
relocate("it.unimi.dsi.fastutil", "com.sk89q.worldedit.sponge.fastutil") {
include(dependency("it.unimi.dsi:fastutil"))

View File

@ -48,18 +48,18 @@ class SpongeEntity implements Entity {
@Override
public BaseEntity getState() {
org.spongepowered.api.entity.Entity entity = entityRef.get();
if (entity != null) {
return new BaseEntity(
EntityType.REGISTRY.get(
entity.type().key(RegistryTypes.ENTITY_TYPE).asString()
),
entity.toContainer().getView(Constants.Sponge.UNSAFE_NBT)
.map(NbtAdapter::adaptToWorldEdit)
.orElse(null)
);
} else {
if (entity == null || entity.vehicle().isPresent()) {
return null;
}
EntityType entityType = EntityType.REGISTRY.get(entity.type().key(RegistryTypes.ENTITY_TYPE).asString());
if (entityType == null) {
return null;
}
return new BaseEntity(entityType,
entity.toContainer().getView(Constants.Sponge.UNSAFE_NBT)
.map(NbtAdapter::adaptToWorldEdit)
.orElse(null)
);
}
@Override

View File

@ -19,14 +19,19 @@
package com.sk89q.worldedit.sponge;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.Constants;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3;
@ -61,6 +66,7 @@
import org.spongepowered.api.block.entity.BlockEntityType;
import org.spongepowered.api.data.persistence.DataContainer;
import org.spongepowered.api.data.persistence.DataView;
import org.spongepowered.api.entity.EntityArchetype;
import org.spongepowered.api.entity.EntityType;
import org.spongepowered.api.entity.EntityTypes;
import org.spongepowered.api.entity.Item;
@ -79,7 +85,11 @@
import java.lang.ref.WeakReference;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@ -458,31 +468,17 @@ public List<? extends Entity> getEntities() {
@Nullable
@Override
public Entity createEntity(Location location, BaseEntity entity) {
ServerWorld world = getWorld();
EntityType<?> entityType = Sponge.game().registry(RegistryTypes.ENTITY_TYPE)
.value(ResourceKey.resolve(entity.getType().getId()));
Vector3d pos = new Vector3d(location.getX(), location.getY(), location.getZ());
org.spongepowered.api.entity.Entity newEnt = world.createEntity(entityType, pos);
if (entity.hasNbtData()) {
newEnt.setRawData(DataContainer.createNew(DataView.SafetyMode.NO_DATA_CLONED)
.set(Constants.Sponge.UNSAFE_NBT, entity.getNbtData()));
Optional<EntityType<?>> entityType = Sponge.game().registry(RegistryTypes.ENTITY_TYPE)
.findValue(ResourceKey.resolve(entity.getType().getId()));
if (!entityType.isPresent()) {
return null;
}
// Overwrite any data set by the NBT application
Vector3 dir = location.getDirection();
newEnt.setLocationAndRotation(
ServerLocation.of(getWorld(), pos),
new Vector3d(dir.getX(), dir.getY(), dir.getZ())
);
if (world.spawnEntity(newEnt)) {
return new SpongeEntity(newEnt);
EntityArchetype.Builder builder = EntityArchetype.builder().type(entityType.get());
CompoundTag nativeTag = entity.getNbtData();
if (nativeTag != null) {
builder.entityData(NbtAdapter.adaptFromWorldEdit(nativeTag));
}
return null;
return builder.build().apply(SpongeAdapter.adapt(location)).map(SpongeEntity::new).orElse(null);
}
@Override