Skip to content

Commit

Permalink
Fix block entities in features/structures
Browse files Browse the repository at this point in the history
Fixes #2669
  • Loading branch information
octylFractal committed Dec 14, 2024
1 parent 8187e67 commit 7daead4
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 138 deletions.
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

0 comments on commit 7daead4

Please sign in to comment.