diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java index dcee6e1462..c8fc548511 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java @@ -28,6 +28,8 @@ import com.google.common.util.concurrent.Futures; 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; @@ -61,6 +63,8 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.entity.EntityTypes; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.Util; @@ -68,6 +72,7 @@ import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; @@ -82,6 +87,7 @@ import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.util.RandomSource; import net.minecraft.util.thread.BlockableEventLoop; import net.minecraft.world.Clearable; import net.minecraft.world.InteractionHand; @@ -105,6 +111,10 @@ import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import net.minecraft.world.phys.BlockHitResult; @@ -178,6 +188,8 @@ public final class PaperweightAdapter implements BukkitImplAdapter { - loadedEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); - return loadedEntity; - }); + createdEntity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); - if (createdEntity != null) { - worldServer.addFreshEntityWithPassengers(createdEntity, SpawnReason.CUSTOM); + worldServer.addFreshEntity(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", LinTagId.LIST.id())) { - net.minecraft.nbt.ListTag nbttaglist = tag.getList("Passengers", LinTagId.COMPOUND.id()); - - for (int i = 0; i < nbttaglist.size(); ++i) { - removeUnwantedEntityTagsRecursively(nbttaglist.getCompound(i)); - } - } - } - @Override public Component getRichBlockName(BlockType blockType) { return TranslatableComponent.of(getBlockFromType(blockType).getDescriptionId()); @@ -877,6 +867,20 @@ public void initializeRegistries() { } } + // Features + for (ResourceLocation name: server.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).keySet()) { + if (ConfiguredFeatureType.REGISTRY.get(name.toString()) == null) { + ConfiguredFeatureType.REGISTRY.register(name.toString(), new ConfiguredFeatureType(name.toString())); + } + } + + // Structures + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.STRUCTURE).keySet()) { + if (StructureType.REGISTRY.get(name.toString()) == null) { + StructureType.REGISTRY.register(name.toString(), new StructureType(name.toString())); + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); biomeRegistry.getTags().forEach(tag -> { @@ -906,6 +910,60 @@ public void sendBiomeUpdates(World world, Iterable chunks) { originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + 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 structureRegistry = originalWorld.registryAccess().lookupOrThrow(Registries.STRUCTURE); + Structure structure = structureRegistry.getValue(ResourceLocation.tryParse(type.id())); + if (structure == null) { + return false; + } + + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + 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.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); + } + } + // ------------------------------------------------------------------------ // Code that is less likely to break // ------------------------------------------------------------------------ diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightServerLevelDelegateProxy.java index 52dac65a09..0219b3dc8e 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightServerLevelDelegateProxy.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightServerLevelDelegateProxy.java @@ -19,221 +19,263 @@ package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_21_4; -import com.fastasyncworldedit.core.internal.exception.FaweException; +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Table; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MaxChangedBlocksException; -import com.sk89q.worldedit.bukkit.WorldEditPlugin; -import com.sk89q.worldedit.bukkit.adapter.Refraction; -import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_4.PaperweightFaweAdapter; -import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.internal.util.LogManagerCompat; -import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.world.entity.EntityTypes; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; +import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; -import net.minecraft.tags.FluidTags; 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.level.material.FluidState; -import net.minecraft.world.phys.AABB; -import org.apache.logging.log4j.Logger; +import net.minecraft.world.phys.Vec3; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.enginehub.linbus.tree.LinCompoundTag; import org.jetbrains.annotations.Nullable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; -public class PaperweightServerLevelDelegateProxy implements InvocationHandler { +public class PaperweightServerLevelDelegateProxy implements InvocationHandler, AutoCloseable { - private static final Logger LOGGER = LogManagerCompat.getLogger(); + private static BlockVector3 adapt(BlockPos blockPos) { + return BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + } - // FAWE start - extent not EditSession - private final Extent editSession; - //FAWE end + private final EditSession editSession; private final ServerLevel serverLevel; - //FAWE start - use FAWE adapter - private final PaperweightFaweAdapter adapter = ((PaperweightFaweAdapter) WorldEditPlugin - .getInstance() - .getBukkitImplAdapter()); - //FAWE end - //FAWE start - force error if method not caught by this instance - private final boolean errorOnPassthrough; - //FAWE end + private final PaperweightAdapter adapter; + private final Map createdBlockEntities = new HashMap<>(); private PaperweightServerLevelDelegateProxy(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { this.editSession = editSession; this.serverLevel = serverLevel; - //FAWE start - this.errorOnPassthrough = false; - //FAWE end + 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) - ); - } - - //FAWE start - force error if method not caught by this instance - private PaperweightServerLevelDelegateProxy(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { - this.editSession = extent; - this.serverLevel = serverLevel; - this.errorOnPassthrough = errorOnPassthrough; + public record LevelAndProxy(WorldGenLevel level, PaperweightServerLevelDelegateProxy proxy) implements AutoCloseable { + @Override + public void close() throws MaxChangedBlocksException { + proxy.close(); + } } - public static WorldGenLevel newInstance(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { - return (WorldGenLevel) Proxy.newProxyInstance( + 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(), - new PaperweightServerLevelDelegateProxy(extent, serverLevel, errorOnPassthrough) + proxy + ), + proxy ); } - //FAWE end @Nullable private BlockEntity getBlockEntity(BlockPos blockPos) { - BlockEntity tileEntity = this.serverLevel.getChunkAt(blockPos).getBlockEntity(blockPos); - if (tileEntity == null) { - return null; - } - BlockEntity newEntity = tileEntity.getType().create(blockPos, getBlockState(blockPos)); - newEntity.loadWithComponents( - (CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock( - blockPos.getX(), - blockPos.getY(), - blockPos.getZ() - ).getNbtReference().getValue()), - this.serverLevel.registryAccess() - ); - - return newEntity; + // 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(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(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, boolean bl) { - try { - return editSession.setBlock(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 FluidState getFluidState(BlockPos pos) { - return getBlockState(pos).getFluidState(); + private boolean removeBlock(BlockPos blockPos, boolean bl) { + return setBlock(blockPos, Blocks.AIR.defaultBlockState()); } - private boolean isWaterAt(BlockPos pos) { - return getBlockState(pos).getFluidState().is(FluidTags.WATER); + private boolean addEntity(Entity entity) { + Vec3 pos = entity.getPosition(0.0f); + Location location = new Location(BukkitAdapter.adapt(serverLevel.getWorld()), pos.x(), pos.y(), pos.z()); + + ResourceLocation id = serverLevel.registryAccess().lookupOrThrow(Registries.ENTITY_TYPE).getKey(entity.getType()); + CompoundTag tag = new CompoundTag(); + entity.saveWithoutId(tag); + BaseEntity baseEntity = new BaseEntity(EntityTypes.get(id.toString()), + LazyReference.from(() -> (LinCompoundTag) adapter.toNativeLin(tag))); + + return editSession.createEntity(location, baseEntity) != null; } @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - //FAWE start - cannot use switch where method names are equal - String methodName = method.getName(); - if (Refraction.pickName("getBlockState", "a_").equals(methodName)) { - if (args.length == 1 && args[0] instanceof BlockPos blockPos) { - // getBlockState - return getBlockState(blockPos); - } - } - if (Refraction.pickName("getBlockEntity", "c_").equals(methodName)) { - if (args.length == 1 && args[0] instanceof BlockPos blockPos) { - // getBlockEntity - return getBlockEntity(blockPos); - } - } - if ("a".equals(methodName) || "setBlock".equals(methodName) || "removeBlock".equals(methodName) || "destroyBlock".equals( - methodName)) { - if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof BlockState blockState) { - // setBlock - return setBlock(blockPos, blockState); - } else if (args.length >= 2 && args[0] instanceof BlockPos blockPos && args[1] instanceof Boolean bl) { - // removeBlock (and also matches destroyBlock) - return removeBlock(blockPos, bl); - } - } - //FAWE start - if (Refraction.pickName("getFluidState", "b_").equals(methodName)) { //net.minecraft.world.level.BlockGetter - if (args.length == 1 && args[0] instanceof BlockPos blockPos) { - return getFluidState(blockPos); - } - } - if (Refraction.pickName("isWaterAt", "z").equals(methodName)) { //net.minecraft.world.level.LevelReader - if (args.length == 1 && args[0] instanceof BlockPos blockPos) { - return isWaterAt(blockPos); - } - } - if (Refraction.pickName("getEntities", "a_").equals(methodName)) { //net.minecraft.world.level.EntityGetter - if (args.length == 2 && args[0] instanceof Entity && args[1] instanceof AABB) { - return new ArrayList<>(); - } - } - // Specific passthroughs that we want to allow - // net.minecraft.world.level.BlockAndTintGetter - if (Refraction.pickName("getRawBrightness", "b").equals(methodName)) { - return method.invoke(this.serverLevel, args); - } - // net.minecraft.world.level.LevelHeightAccessor - if (Refraction.pickName("getMaxBuildHeight", "al").equals(methodName)) { - if (args.length == 0) { - return method.invoke(this.serverLevel, args); - } - } - // net.minecraft.world.level.SignalGetter - if (Refraction.pickName("hasNeighborSignal", "C").equals(methodName)) { - if (args.length == 1 && args[0] instanceof BlockPos) { - return method.invoke(this.serverLevel, args); - } - } - if (Refraction.pickName("getSignal", "c").equals(methodName)) { - if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { - return method.invoke(this.serverLevel, args); - } - } - if (Refraction.pickName("getControlInputSignal", "a").equals(methodName)) { - if (args.length == 3 && args[0] instanceof BlockPos && args[1] instanceof Direction && args[2] instanceof Boolean) { - return method.invoke(this.serverLevel, args); - } - } - if (Refraction.pickName("getDirectSignal", "a").equals(methodName)) { - if (args.length == 2 && args[0] instanceof BlockPos && args[1] instanceof Direction) { - return method.invoke(this.serverLevel, args); - } + public void close() throws MaxChangedBlocksException { + for (Map.Entry 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.toNativeLin(tag))) + ); } - //FAWE start - force error if method not caught by this instance - if (errorOnPassthrough) { - LOGGER.error( - """ - Attempted passthough of method {}. - Method argument types: {} - Method argument values: {} - """, - method.getName(), - Arrays.stream(args).map(a -> a.getClass().getName()).toList(), - Arrays.stream(args).map(Object::toString).toList() + } + + private static void addMethodHandleToTable( + ImmutableTable.Builder table, + String methodName, + MethodHandle methodHandle + ) { + // We want to call these with Object[] args, not plain args + // We skip the first argument, which is our receiver + MethodHandle spreader = methodHandle.asSpreader( + 1, Object[].class, methodHandle.type().parameterCount() - 1 + ); + // We drop the first argument, which is our receiver + // We also drop the return type, which is not important + table.put(methodName, methodHandle.type().dropParameterTypes(0, 1).changeReturnType(void.class), spreader); + } + + private static final Table METHOD_MAP; + + static { + var lookup = MethodHandles.lookup(); + var builder = ImmutableTable.builder(); + try { + addMethodHandleToTable( + builder, + StaticRefraction.GET_BLOCK_STATE, + lookup.unreflect(PaperweightServerLevelDelegateProxy.class.getDeclaredMethod("getBlockState", BlockPos.class)) + ); + + MethodHandle addEntity = lookup.unreflect(PaperweightServerLevelDelegateProxy.class.getDeclaredMethod("addEntity", Entity.class)); + addMethodHandleToTable( + builder, + StaticRefraction.ADD_FRESH_ENTITY_WITH_PASSENGERS_ENTITY, + addEntity + ); + addMethodHandleToTable( + builder, + StaticRefraction.ADD_FRESH_ENTITY_WITH_PASSENGERS_ENTITY_SPAWN_REASON, + // 0 - this, 1 - entity, 2 - reason + MethodHandles.dropArguments(addEntity, 2, CreatureSpawnEvent.SpawnReason.class) ); - throw new FaweException("Method required passthrough."); + addMethodHandleToTable( + builder, + StaticRefraction.ADD_FRESH_ENTITY, + addEntity + ); + addMethodHandleToTable( + builder, + StaticRefraction.ADD_FRESH_ENTITY_SPAWN_REASON, + // 0 - this, 1 - entity, 2 - reason + MethodHandles.dropArguments(addEntity, 2, CreatureSpawnEvent.SpawnReason.class) + ); + + addMethodHandleToTable( + builder, + StaticRefraction.GET_BLOCK_ENTITY, + lookup.unreflect(PaperweightServerLevelDelegateProxy.class.getDeclaredMethod("getBlockEntity", BlockPos.class)) + ); + + MethodHandle setBlock = lookup.unreflect(PaperweightServerLevelDelegateProxy.class.getDeclaredMethod("setBlock", BlockPos.class, BlockState.class)); + addMethodHandleToTable( + builder, + StaticRefraction.SET_BLOCK, + // 0 - this, 1 - blockPos, 2 - blockState, 3 - flags + MethodHandles.dropArguments(setBlock, 3, int.class) + ); + addMethodHandleToTable( + builder, + StaticRefraction.SET_BLOCK_MAX_UPDATE, + // 0 - this, 1 - blockPos, 2 - blockState, 3 - flags, 4 - maxUpdateDepth + MethodHandles.dropArguments(setBlock, 3, int.class, int.class) + ); + + MethodHandle removeBlock = lookup.unreflect(PaperweightServerLevelDelegateProxy.class.getDeclaredMethod("removeBlock", BlockPos.class)); + addMethodHandleToTable( + builder, + StaticRefraction.REMOVE_BLOCK, + // 0 - this, 1 - blockPos, 2 - move + MethodHandles.dropArguments(removeBlock, 2, boolean.class) + ); + addMethodHandleToTable( + builder, + StaticRefraction.DESTROY_BLOCK, + // 0 - this, 1 - blockPos, 2 - drop + MethodHandles.dropArguments(removeBlock, 2, boolean.class) + ); + addMethodHandleToTable( + builder, + StaticRefraction.DESTROY_BLOCK_BREAKING_ENTITY, + // 0 - this, 1 - blockPos, 2 - drop, 3 - breakingEntity + MethodHandles.dropArguments(removeBlock, 2, boolean.class, Entity.class) + ); + addMethodHandleToTable( + builder, + StaticRefraction.DESTROY_BLOCK_BREAKING_ENTITY_MAX_UPDATE, + // 0 - this, 1 - blockPos, 2 - drop, 3 - breakingEntity, 4 - maxUpdateDepth + MethodHandles.dropArguments(removeBlock, 2, boolean.class, Entity.class, int.class) + ); + } catch (IllegalAccessException | NoSuchMethodException e) { + throw new RuntimeException("Failed to bind to own methods", e); } - //FAWE end + METHOD_MAP = builder.build(); + } + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + MethodHandle delegate = METHOD_MAP.get( + // ignore return type, we only need the parameter types + method.getName(), MethodType.methodType(void.class, method.getParameterTypes()) + ); + if (delegate != null) { + return delegate.invoke(this, args); + } return method.invoke(this.serverLevel, args); } diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/StaticRefraction.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/StaticRefraction.java index a44766f44e..e3e01cbe06 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/StaticRefraction.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/StaticRefraction.java @@ -52,6 +52,12 @@ public final class StaticRefraction { * {@code addFreshEntity(Entity entity)}. */ public static final String ADD_FRESH_ENTITY = Refraction.pickName("addFreshEntity", "b"); + /** + * {@code addFreshEntity(Entity entity, CreatureSpawnEvent.SpawnReason reason)}. + */ + public static final String ADD_FRESH_ENTITY_SPAWN_REASON = Refraction.pickName( + "addFreshEntity", "b" + ); /** * {@code getBlockEntity(BlockPos blockPos)}. */ diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/FaweBlockStateListPopulator.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/FaweBlockStateListPopulator.java new file mode 100644 index 0000000000..f4a896a0c4 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/FaweBlockStateListPopulator.java @@ -0,0 +1,133 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_4; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeManager; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkSource; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.lighting.LevelLightEngine; +import net.minecraft.world.level.material.FluidState; +import org.bukkit.craftbukkit.util.BlockStateListPopulator; + +import javax.annotation.Nullable; + +public class FaweBlockStateListPopulator extends BlockStateListPopulator { + + private final ServerLevel world; + + public FaweBlockStateListPopulator(ServerLevel world) { + super(world); + this.world = world; + } + + @Override + public long getSeed() { + return world.getSeed(); + } + + @Override + public ServerLevel getLevel() { + return world.getLevel(); + } + + @Override + public MinecraftServer getServer() { + return world.getServer(); + } + + @Override + public ChunkSource getChunkSource() { + return world.getChunkSource(); + } + + @Override + public ChunkAccess getChunk(final int chunkX, final int chunkZ, final ChunkStatus leastStatus, final boolean create) { + return world.getChunk(chunkX, chunkZ, leastStatus, create); + } + + @Override + public BiomeManager getBiomeManager() { + return world.getBiomeManager(); + } + + @Override + public Holder getUncachedNoiseBiome(final int biomeX, final int biomeY, final int biomeZ) { + return world.getUncachedNoiseBiome(biomeX, biomeY, biomeZ); + } + + @Override + public int getSeaLevel() { + return world.getSeaLevel(); + } + + @Override + public FeatureFlagSet enabledFeatures() { + return world.enabledFeatures(); + } + + @Override + public float getShade(final Direction direction, final boolean shaded) { + return world.getShade(direction, shaded); + } + + @Override + public LevelLightEngine getLightEngine() { + return world.getLightEngine(); + } + + @Nullable + @Override + public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) { + return world.getChunkIfLoadedImmediately(x, z); + } + + @Override + public BlockState getBlockStateIfLoaded(final BlockPos blockposition) { + return world.getBlockStateIfLoaded(blockposition); + } + + @Override + public FluidState getFluidIfLoaded(final BlockPos blockposition) { + return world.getFluidIfLoaded(blockposition); + } + + @Override + public WorldBorder getWorldBorder() { + return world.getWorldBorder(); + } + + @Override + public boolean setBlock(final BlockPos pos, final BlockState state, final int flags, final int maxUpdateDepth) { + return world.setBlock(pos, state, flags, maxUpdateDepth); + } + + @Override + public boolean removeBlock(final BlockPos pos, final boolean move) { + return world.removeBlock(pos, move); + } + + @Override + public boolean destroyBlock(final BlockPos pos, final boolean drop, final Entity breakingEntity, final int maxUpdateDepth) { + return world.destroyBlock(pos, drop, breakingEntity, maxUpdateDepth); + } + + @Override + public BlockState getBlockState(final BlockPos pos) { + return world.getBlockState(pos); + } + + @Override + public boolean setBlock(final BlockPos pos, final BlockState state, final int flags) { + return world.setBlock(pos, state, flags); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java index b0ec904cd3..94df9432a5 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java @@ -11,12 +11,13 @@ import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; import com.fastasyncworldedit.core.util.NbtUtils; +import com.fastasyncworldedit.core.util.TaskManager; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.mojang.serialization.Codec; -import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -47,33 +48,44 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypesCache; import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; +import net.minecraft.core.SectionPos; import net.minecraft.core.WritableRegistry; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerChunkCache; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.StringRepresentable; import net.minecraft.world.entity.Entity; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -82,6 +94,7 @@ import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBlockState; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.craftbukkit.entity.CraftEntity; import org.bukkit.craftbukkit.entity.CraftPlayer; @@ -112,7 +125,7 @@ import static net.minecraft.core.registries.Registries.BIOME; -public final class PaperweightFaweAdapter extends FaweAdapter { +public final class PaperweightFaweAdapter extends FaweAdapter { private static final Logger LOGGER = LogManagerCompat.getLogger(); private static Method CHUNK_HOLDER_WAS_ACCESSIBLE_SINCE_LAST_SAVE; @@ -165,12 +178,12 @@ private static String getEntityId(Entity entity) { return net.minecraft.world.entity.EntityType.getKey(entity.getType()).toString(); } - private static void readEntityIntoTag(Entity entity, net.minecraft.nbt.CompoundTag compoundTag) { + private static void readEntityIntoTag(Entity entity, CompoundTag compoundTag) { entity.save(compoundTag); } @Override - public BukkitImplAdapter getParent() { + public BukkitImplAdapter getParent() { return parent; } @@ -273,7 +286,7 @@ public BaseBlock getFullBlock(final Location location) { // Read the NBT data BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK); if (blockEntity != null) { - net.minecraft.nbt.CompoundTag tag = blockEntity.saveWithId(DedicatedServer.getServer().registryAccess()); + CompoundTag tag = blockEntity.saveWithId(DedicatedServer.getServer().registryAccess()); return state.toBaseBlock((LinCompoundTag) toNativeLin(tag)); } } @@ -294,7 +307,7 @@ public Set getSupportedSideEffects() { } @Override - public WorldNativeAccess createWorldNativeAccess(org.bukkit.World world) { + public WorldNativeAccess createWorldNativeAccess(World world) { return new PaperweightFaweWorldNativeAccess(this, new WeakReference<>(getServerLevel(world))); } @@ -308,7 +321,7 @@ public BaseEntity getEntity(org.bukkit.entity.Entity entity) { String id = getEntityId(mcEntity); EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); Supplier saveTag = () -> { - final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); + final CompoundTag minecraftTag = new CompoundTag(); readEntityIntoTag(mcEntity, minecraftTag); //add Id for AbstractChangeSet to work final LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); @@ -439,7 +452,7 @@ public net.minecraft.world.level.block.state.BlockState adapt(BlockState blockSt } @Override - public void sendFakeChunk(org.bukkit.World world, Player player, ChunkPacket chunkPacket) { + public void sendFakeChunk(World world, Player player, ChunkPacket chunkPacket) { ServerLevel nmsWorld = getServerLevel(world); ChunkHolder map = PaperweightPlatformAdapter.getPlayerChunk(nmsWorld, chunkPacket.getChunkX(), chunkPacket.getChunkZ()); if (map != null && wasAccessibleSinceLastSave(map)) { @@ -474,7 +487,7 @@ public void sendFakeChunk(org.bukkit.World world, Player player, ChunkPacket chu } @Override - public boolean canPlaceAt(org.bukkit.World world, BlockVector3 blockVector3, BlockState blockState) { + public boolean canPlaceAt(World world, BlockVector3 blockVector3, BlockState blockState) { int internalId = BlockStateIdAccess.getBlockStateId(blockState); net.minecraft.world.level.block.state.BlockState blockState1 = Block.stateById(internalId); return blockState1.hasPostProcess( @@ -492,7 +505,7 @@ public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { )), baseItemStack.getAmount() ); - final CompoundTag nbt = (net.minecraft.nbt.CompoundTag) fromNativeLin(baseItemStack.getNbt()); + final CompoundTag nbt = (CompoundTag) fromNativeLin(baseItemStack.getNbt()); if (nbt != null) { final DataComponentPatch patch = COMPONENTS_CODEC .parse(registryAccess.createSerializationContext(NbtOps.INSTANCE), nbt) @@ -525,11 +538,170 @@ protected ServerLevel getServerLevel(final World world) { return ((CraftWorld) world).getHandle(); } + @Override + public boolean generateFeature(ConfiguredFeatureType feature, World world, EditSession editSession, BlockVector3 pt) { + ServerLevel serverLevel = ((CraftWorld) world).getHandle(); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + ConfiguredFeature configuredFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.CONFIGURED_FEATURE) + .getValue(ResourceLocation.tryParse(feature.id())); + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + + Map placed = TaskManager.taskManager().sync(() -> { + serverLevel.captureTreeGeneration = true; + serverLevel.captureBlockStates = true; + try { + if (!configuredFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + Map placedBlocks = populator.getList().stream().collect(Collectors.toMap( + CraftBlockState::getPosition, + craftBlockState -> craftBlockState + )); + placedBlocks.putAll(serverLevel.capturedBlockStates); + return placedBlocks; + } finally { + serverLevel.captureBlockStates = false; + serverLevel.captureTreeGeneration = false; + serverLevel.capturedBlockStates.clear(); + } + }); + + return placeFeatureIntoSession(editSession, populator, placed); + } + + @Override + public boolean generateStructure(StructureType type, World world, EditSession editSession, BlockVector3 pt) { + ServerLevel serverLevel = ((CraftWorld) world).getHandle(); + Registry structureRegistry = serverLevel.registryAccess().lookupOrThrow(Registries.STRUCTURE); + Structure structure = structureRegistry.getValue(ResourceLocation.tryParse(type.id())); + if (structure == null) { + return false; + } + + ServerChunkCache chunkManager = serverLevel.getChunkSource(); + + ChunkPos chunkPos = new ChunkPos(new BlockPos(pt.x(), pt.y(), pt.z())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + Map placed = TaskManager.taskManager().sync(() -> { + serverLevel.captureTreeGeneration = true; + serverLevel.captureBlockStates = true; + try { + StructureStart structureStart = structure.generate( + structureRegistry.wrapAsHolder(structure), + serverLevel.dimension(), + serverLevel.registryAccess(), + chunkManager.getGenerator(), + chunkManager.getGenerator().getBiomeSource(), + chunkManager.randomState(), + serverLevel.getStructureManager(), + serverLevel.getSeed(), + chunkPos, + 0, + populator, + biome -> true + ); + if (!structureStart.isValid()) { + return null; + } 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( + populator, + serverLevel.structureManager(), + chunkManager.getGenerator(), + serverLevel.getRandom(), + new BoundingBox( + chunkPosx.getMinBlockX(), + serverLevel.getMinY(), + chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), + serverLevel.getMaxY(), + chunkPosx.getMaxBlockZ() + ), + chunkPosx + )); + Map placedBlocks = populator.getList().stream().collect(Collectors.toMap( + CraftBlockState::getPosition, + craftBlockState -> craftBlockState + )); + placedBlocks.putAll(serverLevel.capturedBlockStates); + return placedBlocks; + } + } finally { + serverLevel.captureBlockStates = false; + serverLevel.captureTreeGeneration = false; + serverLevel.capturedBlockStates.clear(); + } + }); + + return placeFeatureIntoSession(editSession, populator, placed); + } + + private boolean placeFeatureIntoSession( + final EditSession editSession, + final FaweBlockStateListPopulator populator, + final Map placed + ) { + if (placed == null || placed.isEmpty()) { + return false; + } + + for (Map.Entry entry : placed.entrySet()) { + CraftBlockState craftBlockState = entry.getValue(); + if (entry.getValue() == null) { + continue; + } + BlockPos pos = entry.getKey(); + editSession.setBlock(pos.getX(), pos.getY(), pos.getZ(), BukkitAdapter.adapt(craftBlockState.getBlockData())); + BlockEntity blockEntity = populator.getBlockEntity(pos); + if (blockEntity != null) { + CompoundTag tag = blockEntity.saveWithId(DedicatedServer.getServer().registryAccess()); + editSession.setTile(pos.getX(), pos.getY(), pos.getZ(), (com.sk89q.jnbt.CompoundTag) toNative(tag)); + } + } + return true; + } + + @Override + public void setupFeatures() { + DedicatedServer server = ((CraftServer) Bukkit.getServer()).getServer(); + + // Features + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).keySet()) { + if (ConfiguredFeatureType.REGISTRY.get(name.toString()) == null) { + ConfiguredFeatureType.REGISTRY.register(name.toString(), new ConfiguredFeatureType(name.toString())); + } + } + + // Structures + for (ResourceLocation name : server.registryAccess().lookupOrThrow(Registries.STRUCTURE).keySet()) { + if (StructureType.REGISTRY.get(name.toString()) == null) { + StructureType.REGISTRY.register(name.toString(), new StructureType(name.toString())); + } + } + } + @Override public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { final RegistryAccess.Frozen registryAccess = DedicatedServer.getServer().registryAccess(); final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); - final net.minecraft.nbt.Tag tag = COMPONENTS_CODEC.encodeStart( + final Tag tag = COMPONENTS_CODEC.encodeStart( registryAccess.createSerializationContext(NbtOps.INSTANCE), nmsStack.getComponentsPatch() ).getOrThrow(); @@ -541,22 +713,22 @@ public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { } @Override - public Tag toNative(net.minecraft.nbt.Tag foreign) { + public com.sk89q.jnbt.Tag toNative(Tag foreign) { return parent.toNative(foreign); } @Override - public net.minecraft.nbt.Tag fromNative(Tag foreign) { + public Tag fromNative(com.sk89q.jnbt.Tag foreign) { return parent.fromNative(foreign); } @Override - public boolean regenerate(org.bukkit.World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception { + public boolean regenerate(World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception { return new PaperweightRegen(bukkitWorld, region, target, options).regenerate(); } @Override - public IChunkGet get(org.bukkit.World world, int chunkX, int chunkZ) { + public IChunkGet get(World world, int chunkX, int chunkZ) { return new PaperweightGetBlocks(world, chunkX, chunkZ); }