Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix block entities in features/structures #2674

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.mojang.serialization.Codec;
import com.mojang.serialization.Lifecycle;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseItem;
import com.sk89q.worldedit.blocks.BaseItemStack;
Expand Down Expand Up @@ -117,7 +118,6 @@
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelSettings;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
Expand Down Expand Up @@ -939,33 +939,55 @@ public void initializeRegistries() {

public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) {
ServerLevel originalWorld = ((CraftWorld) world).getHandle();
ConfiguredFeature<?, ?> k = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id()));
ConfiguredFeature<?, ?> feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id()));
ServerChunkCache chunkManager = originalWorld.getChunkSource();
WorldGenLevel proxyLevel = PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this);
return k != null && k.place(proxyLevel, chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z()));
try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel =
PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) {
return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z()));
} catch (MaxChangedBlocksException e) {
throw new RuntimeException(e);
}
}

public boolean generateStructure(StructureType type, World world, EditSession session, BlockVector3 pt) {
ServerLevel originalWorld = ((CraftWorld) world).getHandle();
Registry<Structure> structureRegistry = originalWorld.registryAccess().lookupOrThrow(Registries.STRUCTURE);
Structure k = structureRegistry.getValue(ResourceLocation.tryParse(type.id()));
if (k == null) {
Structure structure = structureRegistry.getValue(ResourceLocation.tryParse(type.id()));
if (structure == null) {
return false;
}

ServerChunkCache chunkManager = originalWorld.getChunkSource();
WorldGenLevel proxyLevel = PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this);
ChunkPos chunkPos = new ChunkPos(new BlockPos(pt.x(), pt.y(), pt.z()));
StructureStart structureStart = k.generate(structureRegistry.wrapAsHolder(k), originalWorld.dimension(), originalWorld.registryAccess(), chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), originalWorld.getStructureManager(), originalWorld.getSeed(), chunkPos, 0, proxyLevel, biome -> true);
try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel =
PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) {
ChunkPos chunkPos = new ChunkPos(new BlockPos(pt.x(), pt.y(), pt.z()));
StructureStart structureStart = structure.generate(
structureRegistry.wrapAsHolder(structure), originalWorld.dimension(), originalWorld.registryAccess(),
chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(),
originalWorld.getStructureManager(), originalWorld.getSeed(), chunkPos, 0,
proxyLevel.level(), biome -> true
);

if (!structureStart.isValid()) {
return false;
} else {
BoundingBox boundingBox = structureStart.getBoundingBox();
ChunkPos min = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.minX()), SectionPos.blockToSectionCoord(boundingBox.minZ()));
ChunkPos max = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.maxX()), SectionPos.blockToSectionCoord(boundingBox.maxZ()));
ChunkPos.rangeClosed(min, max).forEach((chunkPosx) -> structureStart.placeInChunk(proxyLevel, originalWorld.structureManager(), chunkManager.getGenerator(), originalWorld.getRandom(), new BoundingBox(chunkPosx.getMinBlockX(), originalWorld.getMinY(), chunkPosx.getMinBlockZ(), chunkPosx.getMaxBlockX(), originalWorld.getMaxY(), chunkPosx.getMaxBlockZ()), chunkPosx));
return true;
if (!structureStart.isValid()) {
return false;
} else {
BoundingBox boundingBox = structureStart.getBoundingBox();
ChunkPos min = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.minX()), SectionPos.blockToSectionCoord(boundingBox.minZ()));
ChunkPos max = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.maxX()), SectionPos.blockToSectionCoord(boundingBox.maxZ()));
ChunkPos.rangeClosed(min, max).forEach((chunkPosx) ->
structureStart.placeInChunk(
proxyLevel.level(), originalWorld.structureManager(), chunkManager.getGenerator(),
originalWorld.getRandom(),
new BoundingBox(
chunkPosx.getMinBlockX(), originalWorld.getMinY(), chunkPosx.getMinBlockZ(),
chunkPosx.getMaxBlockX(), originalWorld.getMaxY(), chunkPosx.getMaxBlockZ()
), chunkPosx
)
);
return true;
}
} catch (MaxChangedBlocksException e) {
throw new RuntimeException(e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.concurrency.LazyReference;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.entity.EntityTypes;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
Expand All @@ -37,6 +36,8 @@
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
Expand All @@ -50,59 +51,91 @@
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class PaperweightServerLevelDelegateProxy implements InvocationHandler {
public class PaperweightServerLevelDelegateProxy implements InvocationHandler, AutoCloseable {

private static BlockVector3 adapt(BlockPos blockPos) {
return BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ());
}

private final EditSession editSession;
private final ServerLevel serverLevel;
private final PaperweightAdapter adapter;
private final Map<BlockVector3, BlockEntity> createdBlockEntities = new HashMap<>();

private PaperweightServerLevelDelegateProxy(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) {
this.editSession = editSession;
this.serverLevel = serverLevel;
this.adapter = adapter;
}

public static WorldGenLevel newInstance(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) {
return (WorldGenLevel) Proxy.newProxyInstance(
serverLevel.getClass().getClassLoader(),
serverLevel.getClass().getInterfaces(),
new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter)
public record LevelAndProxy(WorldGenLevel level, PaperweightServerLevelDelegateProxy proxy) implements AutoCloseable {
@Override
public void close() throws MaxChangedBlocksException {
proxy.close();
}
}

public static LevelAndProxy newInstance(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) {
PaperweightServerLevelDelegateProxy proxy = new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter);
return new LevelAndProxy(
(WorldGenLevel) Proxy.newProxyInstance(
serverLevel.getClass().getClassLoader(),
serverLevel.getClass().getInterfaces(),
proxy
),
proxy
);
}

@Nullable
private BlockEntity getBlockEntity(BlockPos blockPos) {
BlockEntity tileEntity = this.serverLevel.getChunkAt(blockPos).getBlockEntity(blockPos);
if (tileEntity == null) {
return null;
}
tileEntity.loadWithComponents(
(CompoundTag) adapter.fromNative(this.editSession.getFullBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ())).getNbtReference().getValue()),
this.serverLevel.registryAccess()
);

return tileEntity;
// This doesn't synthesize or load from world. I think editing existing block entities without setting the block
// (in the context of features) should not be supported in the first place.
BlockVector3 pos = adapt(blockPos);
return createdBlockEntities.get(pos);
}

private BlockState getBlockState(BlockPos blockPos) {
return adapter.adapt(this.editSession.getBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ())));
return adapter.adapt(this.editSession.getBlock(adapt(blockPos)));
}

private boolean setBlock(BlockPos blockPos, BlockState blockState) {
try {
return editSession.setBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), adapter.adapt(blockState));
handleBlockEntity(blockPos, blockState);
return editSession.setBlock(adapt(blockPos), adapter.adapt(blockState));
} catch (MaxChangedBlocksException e) {
throw new RuntimeException(e);
}
}

private boolean removeBlock(BlockPos blockPos) {
try {
return editSession.setBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), BlockTypes.AIR.getDefaultState());
} catch (MaxChangedBlocksException e) {
throw new RuntimeException(e);
// For BlockEntity#setBlockState, not sure why it's deprecated
@SuppressWarnings("deprecation")
private void handleBlockEntity(BlockPos blockPos, BlockState blockState) {
BlockVector3 pos = adapt(blockPos);
if (blockState.hasBlockEntity()) {
if (!(blockState.getBlock() instanceof EntityBlock entityBlock)) {
// This will probably never happen, as Mojang's own code assumes that
// hasBlockEntity implies instanceof EntityBlock, but just to be safe...
throw new AssertionError("BlockState has block entity but block is not an EntityBlock: " + blockState);
}
BlockEntity newEntity = entityBlock.newBlockEntity(blockPos, blockState);
if (newEntity != null) {
newEntity.setBlockState(blockState);
createdBlockEntities.put(pos, newEntity);
// Should we load existing NBT here? This is for feature / structure gen so it seems unnecessary.
// But it would align with the behavior of the real setBlock method.
return;
}
}
// Discard any block entity that was previously created if new block is set without block entity
createdBlockEntities.remove(pos);
}

private boolean removeBlock(BlockPos blockPos, boolean bl) {
return setBlock(blockPos, Blocks.AIR.defaultBlockState());
}

private boolean addEntity(Entity entity) {
Expand All @@ -117,6 +150,20 @@ private boolean addEntity(Entity entity) {
return editSession.createEntity(location, baseEntity) != null;
}

@Override
public void close() throws MaxChangedBlocksException {
for (Map.Entry<BlockVector3, BlockEntity> entry : createdBlockEntities.entrySet()) {
BlockVector3 blockPos = entry.getKey();
BlockEntity blockEntity = entry.getValue();
net.minecraft.nbt.CompoundTag tag = blockEntity.saveWithId(serverLevel.registryAccess());
editSession.setBlock(
blockPos,
adapter.adapt(blockEntity.getBlockState())
.toBaseBlock(LazyReference.from(() -> (LinCompoundTag) adapter.toNative(tag)))
);
}
}

private static void addMethodHandleToTable(
ImmutableTable.Builder<String, MethodType, MethodHandle> table,
String methodName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.sk89q.worldedit.world.item.ItemTypes;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
Expand Down Expand Up @@ -195,13 +196,18 @@ public static BaseBlock adapt(BlockEntity blockEntity) {
if (!blockEntity.hasLevel()) {
throw new IllegalArgumentException("BlockEntity must have a level");
}
RegistryAccess registries = blockEntity.getLevel().registryAccess();
return adapt(blockEntity, registries);
}

public static BaseBlock adapt(BlockEntity blockEntity, RegistryAccess registries) {
int blockStateId = Block.getId(blockEntity.getBlockState());
BlockState worldEdit = BlockStateIdAccess.getBlockStateById(blockStateId);
if (worldEdit == null) {
worldEdit = FabricTransmogrifier.transmogToWorldEdit(blockEntity.getBlockState());
}
// Save this outside the reference to ensure it doesn't mutate
CompoundTag savedNative = blockEntity.saveWithId(blockEntity.getLevel().registryAccess());
CompoundTag savedNative = blockEntity.saveWithId(registries);
return worldEdit.toBaseBlock(LazyReference.from(() -> NBTConverter.fromNative(savedNative)));
}

Expand Down
Loading
Loading