diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java index e429fede3b..fc06c2a94f 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java @@ -28,7 +28,7 @@ import com.google.common.util.concurrent.Futures; import com.mojang.datafixers.util.Either; import com.mojang.serialization.Lifecycle; -import com.sk89q.jnbt.NBTConstants; +import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -65,12 +65,15 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; @@ -95,6 +98,7 @@ 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; @@ -107,6 +111,10 @@ import net.minecraft.world.level.chunk.LevelChunk; 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; @@ -519,22 +527,6 @@ public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state } } - // 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()); @@ -642,7 +634,7 @@ public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(PaperweightFakePlayer::new)); @Override - public boolean simulateItemUse(org.bukkit.World world, BlockVector3 position, BaseItem item, Direction face) { + public boolean simulateItemUse(World world, BlockVector3 position, BaseItem item, Direction face) { CraftWorld craftWorld = (CraftWorld) world; ServerLevel worldServer = craftWorld.getHandle(); ItemStack stack = CraftItemStack.asNMSCopy(BukkitAdapter.adapt(item instanceof BaseItemStack @@ -892,6 +884,20 @@ public void initializeRegistries() { } } + // Features + for (ResourceLocation name: server.registryAccess().registryOrThrow(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().registryOrThrow(Registries.STRUCTURE).keySet()) { + if (StructureType.REGISTRY.get(name.toString()) == null) { + StructureType.REGISTRY.register(name.toString(), new StructureType(name.toString())); + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { @@ -921,6 +927,39 @@ public void sendBiomeUpdates(World world, Iterable chunks) { originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); } + @Override + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + ConfiguredFeature k = originalWorld.registryAccess().registryOrThrow(Registries.CONFIGURED_FEATURE).get(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())); + } + + @Override + public boolean generateStructure(StructureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + Structure k = originalWorld.registryAccess().registryOrThrow(Registries.STRUCTURE).get(ResourceLocation.tryParse(type.id())); + if (k == 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(originalWorld.registryAccess(), chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), originalWorld.getStructureManager(), originalWorld.getSeed(), chunkPos, 0, proxyLevel, 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.getMinBuildHeight(), chunkPosx.getMinBlockZ(), chunkPosx.getMaxBlockX(), originalWorld.getMaxBuildHeight(), chunkPosx.getMaxBlockZ()), chunkPosx)); + return true; + } + } + // ------------------------------------------------------------------------ // Code that is less likely to break // ------------------------------------------------------------------------ diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/FaweBlockStateListPopulator.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/FaweBlockStateListPopulator.java new file mode 100644 index 0000000000..a802a2e7af --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/FaweBlockStateListPopulator.java @@ -0,0 +1,132 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; + +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.ChunkStatus; +import net.minecraft.world.level.lighting.LevelLightEngine; +import net.minecraft.world.level.material.FluidState; +import org.bukkit.craftbukkit.v1_20_R2.util.BlockStateListPopulator; +import org.jetbrains.annotations.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_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java index e176743c4a..7f625f4c95 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java @@ -11,11 +11,14 @@ 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.sk89q.jnbt.CompoundTag; 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; @@ -45,11 +48,14 @@ 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.SectionPos; import net.minecraft.core.WritableRegistry; import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; @@ -57,17 +63,24 @@ 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.block.state.properties.DirectionProperty; +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; @@ -76,6 +89,7 @@ import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_20_R2.CraftServer; import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlockState; import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_20_R2.entity.CraftEntity; import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; @@ -299,7 +313,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))); } @@ -448,7 +462,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)) { @@ -483,7 +497,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( @@ -526,6 +540,169 @@ protected ServerLevel getServerLevel(final World world) { return ((CraftWorld) world).getHandle(); } + @Override + public boolean generateFeature(ConfiguredFeatureType feature, World world, EditSession editSession, BlockVector3 pt) { + //FAWE start + ServerLevel serverLevel = ((CraftWorld) world).getHandle(); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + ConfiguredFeature configuredFeature = serverLevel + .registryAccess() + .registryOrThrow(Registries.CONFIGURED_FEATURE) + .get(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); + //FAWE end + } + + @Override + public boolean generateStructure(StructureType type, World world, EditSession editSession, BlockVector3 pt) { + ServerLevel serverLevel = ((CraftWorld) world).getHandle(); + Structure k = serverLevel + .registryAccess() + .registryOrThrow(Registries.STRUCTURE) + .get(ResourceLocation.tryParse(type.id())); + if (k == null) { + return false; + } + + ServerChunkCache chunkManager = serverLevel.getChunkSource(); + + ChunkPos chunkPos = new ChunkPos(new BlockPos(pt.x(), pt.y(), pt.z())); + + //FAWE start + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + Map placed = TaskManager.taskManager().sync(() -> { + serverLevel.captureTreeGeneration = true; + serverLevel.captureBlockStates = true; + try { + StructureStart structureStart = k.generate( + 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.getMinBuildHeight(), + chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), + serverLevel.getMaxBuildHeight(), + 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); + //FAWE end + } + + 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) { + net.minecraft.nbt.CompoundTag tag = blockEntity.saveWithId(); + editSession.setTile(pos.getX(), pos.getY(), pos.getZ(), (CompoundTag) toNative(tag)); + } + } + return true; + } + + @Override + public void setupFeatures() { + DedicatedServer server = ((CraftServer) Bukkit.getServer()).getServer(); + + // Features + for (ResourceLocation name : server.registryAccess().registryOrThrow(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().registryOrThrow(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 ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); @@ -548,12 +725,12 @@ public net.minecraft.nbt.Tag fromNative(Tag 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); } diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightAdapter.java similarity index 92% rename from worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightAdapter.java rename to worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightAdapter.java index ebf1ada46c..23701aea22 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightAdapter.java @@ -28,7 +28,7 @@ import com.google.common.util.concurrent.Futures; import com.mojang.datafixers.util.Either; import com.mojang.serialization.Lifecycle; -import com.sk89q.jnbt.NBTConstants; +import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -65,12 +65,15 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; @@ -95,6 +98,7 @@ 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; @@ -107,6 +111,10 @@ import net.minecraft.world.level.chunk.LevelChunk; 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; @@ -518,22 +526,6 @@ public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state } } - // 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()); @@ -891,6 +883,20 @@ public void initializeRegistries() { } } + // Features + for (ResourceLocation name: server.registryAccess().registryOrThrow(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().registryOrThrow(Registries.STRUCTURE).keySet()) { + if (StructureType.REGISTRY.get(name.toString()) == null) { + StructureType.REGISTRY.register(name.toString(), new StructureType(name.toString())); + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { @@ -920,6 +926,39 @@ public void sendBiomeUpdates(World world, Iterable chunks) { originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); } + @Override + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + ConfiguredFeature k = originalWorld.registryAccess().registryOrThrow(Registries.CONFIGURED_FEATURE).get(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())); + } + + @Override + public boolean generateStructure(StructureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + Structure k = originalWorld.registryAccess().registryOrThrow(Registries.STRUCTURE).get(ResourceLocation.tryParse(type.id())); + if (k == 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(originalWorld.registryAccess(), chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), originalWorld.getStructureManager(), originalWorld.getSeed(), chunkPos, 0, proxyLevel, 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.getMinBuildHeight(), chunkPosx.getMinBlockZ(), chunkPosx.getMaxBlockX(), originalWorld.getMaxBuildHeight(), chunkPosx.getMaxBlockZ()), chunkPosx)); + return true; + } + } + // ------------------------------------------------------------------------ // Code that is less likely to break // ------------------------------------------------------------------------ diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightDataConverters.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightDataConverters.java similarity index 100% rename from worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightDataConverters.java rename to worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightDataConverters.java diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightFakePlayer.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightFakePlayer.java similarity index 100% rename from worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightFakePlayer.java rename to worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightFakePlayer.java diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightServerLevelDelegateProxy.java similarity index 100% rename from worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightServerLevelDelegateProxy.java rename to worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightServerLevelDelegateProxy.java diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightWorldNativeAccess.java similarity index 100% rename from worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R3/PaperweightWorldNativeAccess.java rename to worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightWorldNativeAccess.java diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/FaweBlockStateListPopulator.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/FaweBlockStateListPopulator.java new file mode 100644 index 0000000000..6b9050fed6 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/FaweBlockStateListPopulator.java @@ -0,0 +1,132 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3; + +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.ChunkStatus; +import net.minecraft.world.level.lighting.LevelLightEngine; +import net.minecraft.world.level.material.FluidState; +import org.bukkit.craftbukkit.v1_20_R3.util.BlockStateListPopulator; +import org.jetbrains.annotations.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_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java index c5f4772a03..8cac149990 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java @@ -11,11 +11,14 @@ 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.sk89q.jnbt.CompoundTag; 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; @@ -44,11 +47,14 @@ 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.SectionPos; import net.minecraft.core.WritableRegistry; import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; @@ -56,17 +62,24 @@ 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.block.state.properties.DirectionProperty; +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; @@ -75,6 +88,7 @@ import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_20_R3.CraftServer; import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlockState; import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData; import org.bukkit.craftbukkit.v1_20_R3.entity.CraftEntity; import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; @@ -298,7 +312,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))); } @@ -447,7 +461,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)) { @@ -482,7 +496,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) { net.minecraft.world.level.block.state.BlockState blockState1 = Block.stateById(getOrdinalToIbdID()[blockState.getOrdinal()]); return blockState1.hasPostProcess( getServerLevel(world), @@ -524,6 +538,170 @@ protected ServerLevel getServerLevel(final World world) { return ((CraftWorld) world).getHandle(); } + @Override + public boolean generateFeature(ConfiguredFeatureType feature, World world, EditSession editSession, BlockVector3 pt) { + //FAWE start + ServerLevel serverLevel = ((CraftWorld) world).getHandle(); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + ConfiguredFeature configuredFeature = serverLevel + .registryAccess() + .registryOrThrow(Registries.CONFIGURED_FEATURE) + .get(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); + //FAWE end + } + + @Override + public boolean generateStructure(StructureType type, World world, EditSession editSession, BlockVector3 pt) { + ServerLevel serverLevel = ((CraftWorld) world).getHandle(); + Structure k = serverLevel + .registryAccess() + .registryOrThrow(Registries.STRUCTURE) + .get(ResourceLocation.tryParse(type.id())); + if (k == null) { + return false; + } + + ServerChunkCache chunkManager = serverLevel.getChunkSource(); + + ChunkPos chunkPos = new ChunkPos(new BlockPos(pt.x(), pt.y(), pt.z())); + + //FAWE start + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + Map placed = TaskManager.taskManager().sync(() -> { + serverLevel.captureTreeGeneration = true; + serverLevel.captureBlockStates = true; + try { + StructureStart structureStart = k.generate( + 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.getMinBuildHeight(), + chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), + serverLevel.getMaxBuildHeight(), + 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); + //FAWE end + } + + 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) { + net.minecraft.nbt.CompoundTag tag = blockEntity.saveWithId(); + editSession.setTile(pos.getX(), pos.getY(), pos.getZ(), (CompoundTag) toNative(tag)); + } + } + return true; + } + + @Override + public void setupFeatures() { + DedicatedServer server = ((CraftServer) Bukkit.getServer()).getServer(); + + // Features + for (ResourceLocation name : server.registryAccess().registryOrThrow(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().registryOrThrow(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 ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); @@ -546,12 +724,12 @@ public net.minecraft.nbt.Tag fromNative(Tag 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); } diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java index f45104c142..a9a208de33 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java @@ -28,7 +28,7 @@ import com.google.common.util.concurrent.Futures; import com.mojang.serialization.Codec; import com.mojang.serialization.Lifecycle; -import com.sk89q.jnbt.NBTConstants; +import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -65,6 +65,8 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.Util; import net.minecraft.core.BlockPos; @@ -73,6 +75,7 @@ import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.SectionPos; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; @@ -99,6 +102,7 @@ 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; @@ -111,6 +115,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; @@ -526,22 +534,6 @@ public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state } } - // 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()); @@ -915,6 +907,20 @@ public void initializeRegistries() { } } + // Features + for (ResourceLocation name: server.registryAccess().registryOrThrow(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().registryOrThrow(Registries.STRUCTURE).keySet()) { + if (StructureType.REGISTRY.get(name.toString()) == null) { + StructureType.REGISTRY.register(name.toString(), new StructureType(name.toString())); + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { @@ -944,6 +950,43 @@ public void sendBiomeUpdates(World world, Iterable chunks) { originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); } + @Override + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + ConfiguredFeature k = originalWorld.registryAccess().registryOrThrow(Registries.CONFIGURED_FEATURE).get( + 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())); + } + + @Override + public boolean generateStructure(StructureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + Structure k = originalWorld + .registryAccess() + .registryOrThrow(Registries.STRUCTURE) + .get(ResourceLocation.tryParse(type.id())); + if (k == 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(originalWorld.registryAccess(), chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), originalWorld.getStructureManager(), originalWorld.getSeed(), chunkPos, 0, proxyLevel, 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.getMinBuildHeight(), chunkPosx.getMinBlockZ(), chunkPosx.getMaxBlockX(), originalWorld.getMaxBuildHeight(), chunkPosx.getMaxBlockZ()), chunkPosx)); + return true; + } + } + // ------------------------------------------------------------------------ // Code that is less likely to break // ------------------------------------------------------------------------ diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightServerLevelDelegateProxy.java index f2f32edcbd..a8b5f11cb1 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightServerLevelDelegateProxy.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightServerLevelDelegateProxy.java @@ -19,84 +19,43 @@ package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R4; -import com.fastasyncworldedit.core.internal.exception.FaweException; 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_20_R4.PaperweightFaweAdapter; -import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.world.block.BlockTypes; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; 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.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 com.sk89q.worldedit.math.BlockVector3; +import net.minecraft.server.MinecraftServer; import org.jetbrains.annotations.Nullable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Arrays; public class PaperweightServerLevelDelegateProxy implements InvocationHandler { - private static final Logger LOGGER = LogManagerCompat.getLogger(); - - // 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 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) + 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 static WorldGenLevel newInstance(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { - return (WorldGenLevel) Proxy.newProxyInstance( - serverLevel.getClass().getClassLoader(), - serverLevel.getClass().getInterfaces(), - new PaperweightServerLevelDelegateProxy(extent, serverLevel, errorOnPassthrough) - ); - } - //FAWE end - @Nullable private BlockEntity getBlockEntity(BlockPos blockPos) { BlockEntity tileEntity = this.serverLevel.getChunkAt(blockPos).getBlockEntity(blockPos); @@ -105,24 +64,24 @@ private BlockEntity getBlockEntity(BlockPos blockPos) { } BlockEntity newEntity = tileEntity.getType().create(blockPos, getBlockState(blockPos)); newEntity.loadWithComponents( - (CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock( + (CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock(BlockVector3.at( blockPos.getX(), blockPos.getY(), blockPos.getZ() - ).getNbtReference().getValue()), - this.serverLevel.registryAccess() + )).getNbtReference().getValue()), + MinecraftServer.getServer().registryAccess() ); return newEntity; } private BlockState getBlockState(BlockPos blockPos) { - return adapter.adapt(this.editSession.getBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ())); + return adapter.adapt(this.editSession.getBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()))); } private boolean setBlock(BlockPos blockPos, BlockState blockState) { try { - return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), adapter.adapt(blockState)); + return editSession.setBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), adapter.adapt(blockState)); } catch (MaxChangedBlocksException e) { throw new RuntimeException(e); } @@ -130,110 +89,38 @@ private boolean setBlock(BlockPos blockPos, BlockState blockState) { private boolean removeBlock(BlockPos blockPos, boolean bl) { try { - return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), BlockTypes.AIR.getDefaultState()); + return editSession.setBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), BlockTypes.AIR.getDefaultState()); } catch (MaxChangedBlocksException e) { throw new RuntimeException(e); } } - private FluidState getFluidState(BlockPos pos) { - return getBlockState(pos).getFluidState(); - } - - private boolean isWaterAt(BlockPos pos) { - return getBlockState(pos).getFluidState().is(FluidTags.WATER); - } - @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); + switch (method.getName()) { + case "a_", "getBlockState" -> { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockState + return getBlockState(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); + case "c_", "getBlockEntity" -> { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockEntity + return getBlockEntity(blockPos); + } } - } - //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); + case "a", "setBlock", "removeBlock", "destroyBlock" -> { + 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); + } } + default -> { } } - 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); - } - } - //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() - ); - throw new FaweException("Method required passthrough."); - } - //FAWE end - return method.invoke(this.serverLevel, args); } diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/FaweBlockStateListPopulator.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/FaweBlockStateListPopulator.java new file mode 100644 index 0000000000..9a1e20c712 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/FaweBlockStateListPopulator.java @@ -0,0 +1,132 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R4; + +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 org.jetbrains.annotations.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_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java index 6c5a5330ec..b03725f1d3 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java @@ -11,12 +11,15 @@ 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.CompoundTag; 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 +50,42 @@ 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.network.protocol.game.ClientboundLevelChunkWithLightPacket; 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.block.state.properties.DirectionProperty; +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; @@ -308,7 +321,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))); } @@ -457,7 +470,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)) { @@ -492,7 +505,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( @@ -508,7 +521,7 @@ public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { registryAccess.registryOrThrow(Registries.ITEM).get(ResourceLocation.tryParse(baseItemStack.getType().id())), baseItemStack.getAmount() ); - final CompoundTag nbt = (net.minecraft.nbt.CompoundTag) fromNative(baseItemStack.getNbtData()); + final net.minecraft.nbt.CompoundTag nbt = (net.minecraft.nbt.CompoundTag) fromNative(baseItemStack.getNbtData()); if (nbt != null) { final DataComponentPatch patch = COMPONENTS_CODEC .parse(registryAccess.createSerializationContext(NbtOps.INSTANCE), nbt) @@ -541,6 +554,169 @@ protected ServerLevel getServerLevel(final World world) { return ((CraftWorld) world).getHandle(); } + @Override + public boolean generateFeature(ConfiguredFeatureType feature, World world, EditSession editSession, BlockVector3 pt) { + //FAWE start + ServerLevel serverLevel = ((CraftWorld) world).getHandle(); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + ConfiguredFeature configuredFeature = serverLevel + .registryAccess() + .registryOrThrow(Registries.CONFIGURED_FEATURE) + .get(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); + //FAWE end + } + + @Override + public boolean generateStructure(StructureType type, World world, EditSession editSession, BlockVector3 pt) { + ServerLevel serverLevel = ((CraftWorld) world).getHandle(); + Structure k = serverLevel + .registryAccess() + .registryOrThrow(Registries.STRUCTURE) + .get(ResourceLocation.tryParse(type.id())); + if (k == null) { + return false; + } + + ServerChunkCache chunkManager = serverLevel.getChunkSource(); + + ChunkPos chunkPos = new ChunkPos(new BlockPos(pt.x(), pt.y(), pt.z())); + + //FAWE start + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + Map placed = TaskManager.taskManager().sync(() -> { + serverLevel.captureTreeGeneration = true; + serverLevel.captureBlockStates = true; + try { + StructureStart structureStart = k.generate( + 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.getMinBuildHeight(), + chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), + serverLevel.getMaxBuildHeight(), + 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); + //FAWE end + } + + 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) { + net.minecraft.nbt.CompoundTag tag = blockEntity.saveWithId(DedicatedServer.getServer().registryAccess()); + editSession.setTile(pos.getX(), pos.getY(), pos.getZ(), (CompoundTag) toNative(tag)); + } + } + return true; + } + + @Override + public void setupFeatures() { + DedicatedServer server = ((CraftServer) Bukkit.getServer()).getServer(); + + // Features + for (ResourceLocation name : server.registryAccess().registryOrThrow(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().registryOrThrow(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(); @@ -570,12 +746,12 @@ public net.minecraft.nbt.Tag fromNative(Tag 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); } diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java index 995b221359..ea7b5917e9 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java @@ -28,6 +28,7 @@ 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.WorldEditException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -64,6 +65,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; @@ -72,6 +75,7 @@ import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.SectionPos; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; @@ -98,6 +102,7 @@ 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; @@ -110,6 +115,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; @@ -500,48 +509,27 @@ public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state CraftWorld craftWorld = ((CraftWorld) location.getWorld()); ServerLevel worldServer = craftWorld.getHandle(); - String entityId = state.getType().id(); + Entity createdEntity = createEntityFromId(state.getType().id(), craftWorld.getHandle()); - LinCompoundTag nativeTag = state.getNbt(); - net.minecraft.nbt.CompoundTag tag; - if (nativeTag != null) { - tag = (net.minecraft.nbt.CompoundTag) fromNativeLin(nativeTag); - removeUnwantedEntityTagsRecursively(tag); - } else { - tag = new net.minecraft.nbt.CompoundTag(); - } - - tag.putString("id", entityId); + if (createdEntity != null) { + LinCompoundTag nativeTag = state.getNbt(); + if (nativeTag != null) { + net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) fromNativeLin(nativeTag); + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + readTagIntoEntity(tag, createdEntity); + } - Entity createdEntity = EntityType.loadEntityRecursive(tag, craftWorld.getHandle(), (loadedEntity) -> { - 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()); @@ -921,6 +909,20 @@ public void initializeRegistries() { } } + // Features + for (ResourceLocation name: server.registryAccess().registryOrThrow(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().registryOrThrow(Registries.STRUCTURE).keySet()) { + if (StructureType.REGISTRY.get(name.toString()) == null) { + StructureType.REGISTRY.register(name.toString(), new StructureType(name.toString())); + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { @@ -950,6 +952,39 @@ public void sendBiomeUpdates(World world, Iterable chunks) { originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); } + @Override + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + ConfiguredFeature k = originalWorld.registryAccess().registryOrThrow(Registries.CONFIGURED_FEATURE).get(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())); + } + + @Override + public boolean generateStructure(StructureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + Structure k = originalWorld.registryAccess().registryOrThrow(Registries.STRUCTURE).get(ResourceLocation.tryParse(type.id())); + if (k == 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(originalWorld.registryAccess(), chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), originalWorld.getStructureManager(), originalWorld.getSeed(), chunkPos, 0, proxyLevel, 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.getMinBuildHeight(), chunkPosx.getMinBlockZ(), chunkPosx.getMaxBlockX(), originalWorld.getMaxBuildHeight(), chunkPosx.getMaxBlockZ()), chunkPosx)); + return true; + } + } + // ------------------------------------------------------------------------ // Code that is less likely to break // ------------------------------------------------------------------------ diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightServerLevelDelegateProxy.java index a992e936c8..43506eb044 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightServerLevelDelegateProxy.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightServerLevelDelegateProxy.java @@ -19,83 +19,42 @@ package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_21_R1; -import com.fastasyncworldedit.core.internal.exception.FaweException; 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_R1.PaperweightFaweAdapter; -import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.world.block.BlockTypes; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; 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.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 com.sk89q.worldedit.math.BlockVector3; +import net.minecraft.server.MinecraftServer; import org.jetbrains.annotations.Nullable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Arrays; public class PaperweightServerLevelDelegateProxy implements InvocationHandler { - private static final Logger LOGGER = LogManagerCompat.getLogger(); - - // 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 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 static WorldGenLevel newInstance(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { - return (WorldGenLevel) Proxy.newProxyInstance( - serverLevel.getClass().getClassLoader(), - serverLevel.getClass().getInterfaces(), - new PaperweightServerLevelDelegateProxy(extent, serverLevel, errorOnPassthrough) + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter) ); } - //FAWE end @Nullable private BlockEntity getBlockEntity(BlockPos blockPos) { @@ -105,24 +64,24 @@ private BlockEntity getBlockEntity(BlockPos blockPos) { } BlockEntity newEntity = tileEntity.getType().create(blockPos, getBlockState(blockPos)); newEntity.loadWithComponents( - (CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock( + (CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock(BlockVector3.at( blockPos.getX(), blockPos.getY(), blockPos.getZ() - ).getNbtReference().getValue()), - this.serverLevel.registryAccess() + )).getNbtReference().getValue()), + MinecraftServer.getServer().registryAccess() ); return newEntity; } private BlockState getBlockState(BlockPos blockPos) { - return adapter.adapt(this.editSession.getBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ())); + return adapter.adapt(this.editSession.getBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()))); } private boolean setBlock(BlockPos blockPos, BlockState blockState) { try { - return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), adapter.adapt(blockState)); + return editSession.setBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), adapter.adapt(blockState)); } catch (MaxChangedBlocksException e) { throw new RuntimeException(e); } @@ -130,109 +89,38 @@ private boolean setBlock(BlockPos blockPos, BlockState blockState) { private boolean removeBlock(BlockPos blockPos, boolean bl) { try { - return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), BlockTypes.AIR.getDefaultState()); + return editSession.setBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), BlockTypes.AIR.getDefaultState()); } catch (MaxChangedBlocksException e) { throw new RuntimeException(e); } } - private FluidState getFluidState(BlockPos pos) { - return getBlockState(pos).getFluidState(); - } - - private boolean isWaterAt(BlockPos pos) { - return getBlockState(pos).getFluidState().is(FluidTags.WATER); - } - @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); + switch (method.getName()) { + case "a_", "getBlockState" -> { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockState + return getBlockState(blockPos); + } } - } - //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); + case "c_", "getBlockEntity" -> { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockEntity + return getBlockEntity(blockPos); + } } - } - // 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); + case "a", "setBlock", "removeBlock", "destroyBlock" -> { + 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); + } } + default -> { } } - //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() - ); - throw new FaweException("Method required passthrough."); - } - //FAWE end return method.invoke(this.serverLevel, args); } diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/FaweBlockStateListPopulator.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/FaweBlockStateListPopulator.java new file mode 100644 index 0000000000..6add96e108 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/FaweBlockStateListPopulator.java @@ -0,0 +1,133 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1; + +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.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeManager; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.flag.FeatureFlagSet; +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/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java index 415a462c93..7c17aa5d89 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java @@ -11,12 +11,15 @@ 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.CompoundTag; 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 +50,43 @@ 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.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.block.state.properties.DirectionProperty; +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 +95,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; @@ -308,7 +322,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))); } @@ -457,7 +471,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)) { @@ -492,7 +506,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( @@ -508,7 +522,7 @@ public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { registryAccess.registryOrThrow(Registries.ITEM).get(ResourceLocation.tryParse(baseItemStack.getType().id())), baseItemStack.getAmount() ); - final CompoundTag nbt = (net.minecraft.nbt.CompoundTag) fromNative(baseItemStack.getNbtData()); + final net.minecraft.nbt.CompoundTag nbt = (net.minecraft.nbt.CompoundTag) fromNativeLin(baseItemStack.getNbt()); if (nbt != null) { final DataComponentPatch patch = COMPONENTS_CODEC .parse(registryAccess.createSerializationContext(NbtOps.INSTANCE), nbt) @@ -541,6 +555,165 @@ 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() + .registryOrThrow(Registries.CONFIGURED_FEATURE) + .get(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(); + Structure k = serverLevel + .registryAccess() + .registryOrThrow(Registries.STRUCTURE) + .get(ResourceLocation.tryParse(type.id())); + if (k == 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 = k.generate( + 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.getMinBuildHeight(), + chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), + serverLevel.getMaxBuildHeight(), + 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) { + net.minecraft.nbt.CompoundTag tag = blockEntity.saveWithId(DedicatedServer.getServer().registryAccess()); + editSession.setTile(pos.getX(), pos.getY(), pos.getZ(), (CompoundTag) toNative(tag)); + } + } + return true; + } + + @Override + public void setupFeatures() { + DedicatedServer server = ((CraftServer) Bukkit.getServer()).getServer(); + + // Features + for (ResourceLocation name : server.registryAccess().registryOrThrow(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().registryOrThrow(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(); @@ -570,12 +743,12 @@ public net.minecraft.nbt.Tag fromNative(Tag 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); } diff --git a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightAdapter.java index d3497145c9..c44677ae5f 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightAdapter.java @@ -28,6 +28,7 @@ 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.WorldEditException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -61,6 +62,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 +71,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 +86,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; @@ -94,6 +99,7 @@ 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; @@ -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()); @@ -884,6 +875,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 -> { @@ -913,6 +918,39 @@ public void sendBiomeUpdates(World world, Iterable chunks) { originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); } + @Override + 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())); + 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())); + } + + @Override + public boolean generateStructure(StructureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + Structure k = originalWorld.registryAccess().lookupOrThrow(Registries.STRUCTURE).getValue(ResourceLocation.tryParse(type.id())); + if (k == 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(originalWorld.registryAccess(), chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), originalWorld.getStructureManager(), originalWorld.getSeed(), chunkPos, 0, proxyLevel, 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; + } + } + // ------------------------------------------------------------------------ // Code that is less likely to break // ------------------------------------------------------------------------ diff --git a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightServerLevelDelegateProxy.java index 4b660ccf34..6fd6085705 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightServerLevelDelegateProxy.java +++ b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_3/PaperweightServerLevelDelegateProxy.java @@ -19,83 +19,42 @@ package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_21_3; -import com.fastasyncworldedit.core.internal.exception.FaweException; 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_3.PaperweightFaweAdapter; -import com.sk89q.worldedit.extent.Extent; -import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.world.block.BlockTypes; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; 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.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 com.sk89q.worldedit.math.BlockVector3; +import net.minecraft.server.MinecraftServer; import org.jetbrains.annotations.Nullable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Arrays; public class PaperweightServerLevelDelegateProxy implements InvocationHandler { - private static final Logger LOGGER = LogManagerCompat.getLogger(); - - // 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 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 static WorldGenLevel newInstance(Extent extent, ServerLevel serverLevel, boolean errorOnPassthrough) { - return (WorldGenLevel) Proxy.newProxyInstance( - serverLevel.getClass().getClassLoader(), - serverLevel.getClass().getInterfaces(), - new PaperweightServerLevelDelegateProxy(extent, serverLevel, errorOnPassthrough) + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter) ); } - //FAWE end @Nullable private BlockEntity getBlockEntity(BlockPos blockPos) { @@ -105,24 +64,24 @@ private BlockEntity getBlockEntity(BlockPos blockPos) { } BlockEntity newEntity = tileEntity.getType().create(blockPos, getBlockState(blockPos)); newEntity.loadWithComponents( - (CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock( + (CompoundTag) adapter.fromNativeLin(this.editSession.getFullBlock(BlockVector3.at( blockPos.getX(), blockPos.getY(), blockPos.getZ() - ).getNbtReference().getValue()), - this.serverLevel.registryAccess() + )).getNbtReference().getValue()), + MinecraftServer.getServer().registryAccess() ); return newEntity; } private BlockState getBlockState(BlockPos blockPos) { - return adapter.adapt(this.editSession.getBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ())); + return adapter.adapt(this.editSession.getBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()))); } private boolean setBlock(BlockPos blockPos, BlockState blockState) { try { - return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), adapter.adapt(blockState)); + return editSession.setBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), adapter.adapt(blockState)); } catch (MaxChangedBlocksException e) { throw new RuntimeException(e); } @@ -130,109 +89,38 @@ private boolean setBlock(BlockPos blockPos, BlockState blockState) { private boolean removeBlock(BlockPos blockPos, boolean bl) { try { - return editSession.setBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ(), BlockTypes.AIR.getDefaultState()); + return editSession.setBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), BlockTypes.AIR.getDefaultState()); } catch (MaxChangedBlocksException e) { throw new RuntimeException(e); } } - private FluidState getFluidState(BlockPos pos) { - return getBlockState(pos).getFluidState(); - } - - private boolean isWaterAt(BlockPos pos) { - return getBlockState(pos).getFluidState().is(FluidTags.WATER); - } - @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); + switch (method.getName()) { + case "a_", "getBlockState" -> { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockState + return getBlockState(blockPos); + } } - } - //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); + case "c_", "getBlockEntity" -> { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockEntity + return getBlockEntity(blockPos); + } } - } - // 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); + case "a", "setBlock", "removeBlock", "destroyBlock" -> { + 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); + } } + default -> { } } - //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() - ); - throw new FaweException("Method required passthrough."); - } - //FAWE end return method.invoke(this.serverLevel, args); } diff --git a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/FaweBlockStateListPopulator.java b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/FaweBlockStateListPopulator.java new file mode 100644 index 0000000000..e8c212e03e --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/FaweBlockStateListPopulator.java @@ -0,0 +1,133 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_3; + +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_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightFaweAdapter.java index 47aba48713..452bbfcfe4 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/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(); + Structure k = serverLevel + .registryAccess() + .lookupOrThrow(Registries.STRUCTURE) + .getValue(ResourceLocation.tryParse(type.id())); + if (k == 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 = k.generate( + 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); } 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); } diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/IBukkitAdapter.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/IBukkitAdapter.java index 16e959d273..b8abff0ec3 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/IBukkitAdapter.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/IBukkitAdapter.java @@ -396,4 +396,12 @@ default List getEntities(org.bukkit.World world) { return TaskManager.taskManager().sync(world::getEntities); } + /** + * Import Minecraft internal features into FAWE. Should be executed after worlds loading (in order to capture datapacks) + * + * @since TODO + */ + default void setupFeatures() { + } + } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index 9e4da54019..ce02dd3682 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -54,6 +54,8 @@ import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.weather.WeatherType; import com.sk89q.worldedit.world.weather.WeatherTypes; import io.papermc.lib.PaperLib; @@ -519,6 +521,26 @@ public boolean canPlaceAt(BlockVector3 position, com.sk89q.worldedit.world.block return true; } + @Override + public boolean generateFeature(ConfiguredFeatureType type, EditSession editSession, BlockVector3 position) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + if (adapter != null) { + return adapter.generateFeature(type, getWorld(), editSession, position); + } + // No adapter, we can't generate this. + return false; + } + + @Override + public boolean generateStructure(StructureType type, EditSession editSession, BlockVector3 position) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + if (adapter != null) { + return adapter.generateStructure(type, getWorld(), editSession, position); + } + // No adapter, we can't generate this. + return false; + } + private static volatile boolean hasWarnedImplError = false; @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index c11f8bd73b..4d774d3973 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -21,8 +21,8 @@ import com.fastasyncworldedit.bukkit.BukkitPermissionAttachmentManager; import com.fastasyncworldedit.bukkit.FaweBukkit; -import com.fastasyncworldedit.core.util.UpdateNotification; import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.util.UpdateNotification; import com.fastasyncworldedit.core.util.WEManager; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; @@ -300,6 +300,7 @@ private void setupWorldData() { setupTags(); setupBiomes(false); // FAWE - load biomes later. Initialize biomes twice to allow for the registry to be present for // plugins requiring WE biomes during startup, as well as allowing custom biomes loaded later on to be present in WE. + ((BukkitImplAdapter) adapter.value().get()).setupFeatures(); WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent(platform)); } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java index 986b348d33..12e96abc95 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java @@ -30,6 +30,7 @@ import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; import com.sk89q.jnbt.LinBusConverter; import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -50,6 +51,8 @@ import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; +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 org.bukkit.Keyed; @@ -321,6 +324,37 @@ default void initializeRegistries() { * @param chunks a list of chunk coordinates to send biome updates for */ default void sendBiomeUpdates(World world, Iterable chunks) { + + } + + /** + * Generates a Minecraft feature at the given location. + * + * @param feature The feature + * @param world The world + * @param session The EditSession + * @param pt The location + * @return If it succeeded + * + * @since TODO + */ + default boolean generateFeature(ConfiguredFeatureType feature, World world, EditSession session, BlockVector3 pt) { + throw new UnsupportedOperationException("This adapter does not support generating features."); + } + + /** + * Generates a Minecraft structure at the given location. + * + * @param feature The feature + * @param world The world + * @param session The EditSession + * @param pt The location + * @return If it succeeded + * + * @since TODO + */ + default boolean generateStructure(StructureType feature, World world, EditSession session, BlockVector3 pt) { + throw new UnsupportedOperationException("This adapter does not support generating features."); } //FAWE start diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/factory/StructureGeneratorFactory.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/factory/StructureGeneratorFactory.java new file mode 100644 index 0000000000..3a74f14a0c --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/factory/StructureGeneratorFactory.java @@ -0,0 +1,46 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.fastasyncworldedit.core.command.factory; + +import com.fastasyncworldedit.core.function.generator.StructureGenerator; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.function.Contextual; +import com.sk89q.worldedit.function.EditContext; +import com.sk89q.worldedit.world.generation.StructureType; + +public final class StructureGeneratorFactory implements Contextual { + + private final StructureType type; + + public StructureGeneratorFactory(StructureType type) { + this.type = type; + } + + @Override + public StructureGenerator createFromContext(EditContext input) { + return new StructureGenerator((EditSession) input.getDestination(), type); + } + + @Override + public String toString() { + return "structure of type " + type; + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/FeaturePlacer.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/FeaturePlacer.java new file mode 100644 index 0000000000..ae47acadaf --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/FeaturePlacer.java @@ -0,0 +1,77 @@ +package com.fastasyncworldedit.core.command.tool; + +import com.fastasyncworldedit.core.configuration.Caption; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.command.tool.BlockTool; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; + +import javax.annotation.Nullable; + +/** + * Places a feature + * + * @since TODO + */ +public class FeaturePlacer implements BlockTool { + + private final ConfiguredFeatureType feature; + + /** + * New instance + * + * @since TODO + */ + public FeaturePlacer(ConfiguredFeatureType feature) { + this.feature = feature; + } + + @Override + public boolean canUse(Actor player) { + return player.hasPermission("worldedit.tool.feature"); + } + + @Override + public boolean actPrimary( + Platform server, + LocalConfiguration config, + Player player, + LocalSession session, + Location clicked, + @Nullable Direction face + ) { + + try (EditSession editSession = session.createEditSession(player)) { + try { + boolean successful = false; + + final BlockVector3 pos = clicked.toVector().add().toBlockPoint(); + for (int i = 0; i < 10; i++) { + if (feature.place(editSession, pos)) { + successful = true; + break; + } + } + + if (!successful) { + player.print(Caption.of("worldedit.tool.feature.failed")); + } + } catch (MaxChangedBlocksException e) { + player.print(Caption.of("worldedit.tool.max-block-changes")); + } finally { + session.remember(editSession); + } + } + + return true; + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/StructurePlacer.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/StructurePlacer.java new file mode 100644 index 0000000000..798e74aea9 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/command/tool/StructurePlacer.java @@ -0,0 +1,77 @@ +package com.fastasyncworldedit.core.command.tool; + +import com.fastasyncworldedit.core.configuration.Caption; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.command.tool.BlockTool; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.generation.StructureType; + +import javax.annotation.Nullable; + +/** + * Places a structure + * + * @since TODO + */ +public class StructurePlacer implements BlockTool { + + private final StructureType structure; + + /** + * New instance + * + * @since TODO + */ + public StructurePlacer(StructureType structure) { + this.structure = structure; + } + + @Override + public boolean canUse(Actor player) { + return player.hasPermission("worldedit.tool.feature"); + } + + @Override + public boolean actPrimary( + Platform server, + LocalConfiguration config, + Player player, + LocalSession session, + Location clicked, + @Nullable Direction face + ) { + + try (EditSession editSession = session.createEditSession(player)) { + try { + boolean successful = false; + + final BlockVector3 pos = clicked.toVector().add().toBlockPoint(); + for (int i = 0; i < 10; i++) { + if (structure.place(editSession, pos)) { + successful = true; + break; + } + } + + if (!successful) { + player.print(Caption.of("worldedit.tool.feature.failed")); + } + } catch (MaxChangedBlocksException e) { + player.print(Caption.of("worldedit.tool.max-block-changes")); + } finally { + session.remember(editSession); + } + } + + return true; + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/generator/StructureGenerator.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/generator/StructureGenerator.java new file mode 100644 index 0000000000..c99677126b --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/generator/StructureGenerator.java @@ -0,0 +1,37 @@ +package com.fastasyncworldedit.core.function.generator; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.function.RegionFunction; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.generation.StructureType; + +/** + * Generate a structure at the given location + * + * @since TODO + */ +public class StructureGenerator implements RegionFunction { + + private final StructureType structureType; + private final EditSession editSession; + + /** + * Create a new instance. + * + * @param editSession the edit session + * @param structureType the structure type + * + * @since TODO + */ + public StructureGenerator(EditSession editSession, StructureType structureType) { + this.editSession = editSession; + this.structureType = structureType; + } + + @Override + public boolean apply(BlockVector3 position) throws WorldEditException { + return editSession.getWorld().generateStructure(structureType, editSession, position); + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java index 616e90b375..8421df551c 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java @@ -37,6 +37,8 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.weather.WeatherType; import javax.annotation.Nullable; @@ -133,12 +135,10 @@ public boolean playEffect(Vector3 position, int type, int data) { return parent.playEffect(position, type, data); } - //FAWE start - allow block break effect of non-legacy blocks @Override public boolean playBlockBreakEffect(Vector3 position, BlockType type) { return parent.playBlockBreakEffect(position, type); } - //FAWE end @Override public boolean queueBlockBreakEffect(Platform server, BlockVector3 position, BlockType blockType, double priority) { @@ -207,12 +207,10 @@ public String getName() { return parent.getName(); } - //FAWE start - allow history to read an unloaded world's name @Override public String getNameUnsafe() { return parent.getNameUnsafe(); } - //FAWE end @Override public > boolean setBlock(BlockVector3 position, B block, boolean notifyAndLight) throws @@ -262,12 +260,15 @@ public void run(Object value) { }); } - //FAWE start @Override public Collection getBlockDrops(final BlockVector3 position) { return TaskManager.taskManager().sync(() -> parent.getBlockDrops(position)); } - //FAWE end + + @Override + public boolean canPlaceAt(final BlockVector3 position, final BlockState blockState) { + return parent.canPlaceAt(position, blockState); + } @Override public boolean regenerate(Region region, EditSession session) { @@ -289,6 +290,16 @@ public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession } } + @Override + public boolean generateStructure(final StructureType type, final EditSession editSession, final BlockVector3 position) { + return parent.generateStructure(type, editSession, position); + } + + @Override + public boolean generateFeature(final ConfiguredFeatureType type, final EditSession editSession, final BlockVector3 position) { + return parent.generateFeature(type, editSession, position); + } + @Override public BlockVector3 getSpawnPosition() { return parent.getSpawnPosition(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java index 4daa116edc..9f6f9de55d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -148,6 +148,8 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.registry.LegacyMapper; import org.apache.logging.log4j.Logger; @@ -4052,5 +4054,33 @@ public int makeBlob( } return changes; } + + /** + * Generate a feature into this EditSession + * + * @param feature feature to generate + * @param position position to generate at + * @return blocks affected + * + * @since TODO + */ + public int generateFeature(ConfiguredFeatureType feature, BlockVector3 position) { + feature.place(this, position); + return changes; + } + + /** + * Generate a structure into this EditSession + * + * @param structure structure to generate + * @param position position to generate at + * @return blocks affected + * + * @since TODO + */ + public int generateStructure(StructureType structure, BlockVector3 position) { + structure.place(this, position); + return changes; + } //FAWE end } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index e6a211a4bb..589155767e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -20,6 +20,7 @@ package com.sk89q.worldedit.command; import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.command.factory.StructureGeneratorFactory; import com.fastasyncworldedit.core.command.tool.brush.BlendBall; import com.fastasyncworldedit.core.command.tool.brush.BlobBrush; import com.fastasyncworldedit.core.command.tool.brush.BrushSettings; @@ -66,6 +67,7 @@ import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.command.argument.Arguments; +import com.sk89q.worldedit.command.factory.FeatureGeneratorFactory; import com.sk89q.worldedit.command.factory.ReplaceFactory; import com.sk89q.worldedit.command.factory.TreeGeneratorFactory; import com.sk89q.worldedit.command.tool.BrushTool; @@ -115,6 +117,8 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import org.anarres.parallelgzip.ParallelGZIPOutputStream; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; @@ -1556,7 +1560,43 @@ public void butcherBrush( set(context, new ButcherBrush(flags), "worldedit.brush.butcher").setSize(radius); } + @Command( + name = "feature", + desc = "Feature brush, paints Minecraft generation features" + ) + @CommandPermissions("worldedit.brush.feature") + public void feature(Player player, LocalSession localSession, + @Arg(desc = "The shape of the region") + RegionFactory shape, + @Arg(desc = "The size of the brush", def = "5") + double radius, + @Arg(desc = "The density of the brush", def = "5") + double density, + @Arg(desc = "The type of feature to use") + ConfiguredFeatureType type) throws WorldEditException { + setOperationBasedBrush(player, localSession, radius, + new Paint(new FeatureGeneratorFactory(type), density / 100), shape, "worldedit.brush.feature"); + } + //FAWE start + @Command( + name = "structure", + desc = "Structure brush, paints Minecraft generation structures" + ) + @CommandPermissions("worldedit.brush.feature") + public void structure(Player player, LocalSession localSession, + @Arg(desc = "The shape of the region") + RegionFactory shape, + @Arg(desc = "The size of the brush", def = "5") + double radius, + @Arg(desc = "The density of the brush", def = "5") + double density, + @Arg(desc = "The type of feature to use") + StructureType type) throws WorldEditException { + setOperationBasedBrush(player, localSession, radius, + new Paint(new StructureGeneratorFactory(type), density / 100), shape, "worldedit.brush.structure"); + } + public BrushSettings process(Player player, Arguments arguments, BrushSettings settings) throws WorldEditException { LocalSession session = worldEdit.getSessionManager().get(player); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index f2a3c80ed4..93fb6bb4f9 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -57,13 +57,15 @@ import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.param.Arg; import org.enginehub.piston.annotation.param.Switch; import org.jetbrains.annotations.Range; -import java.awt.RenderingHints; +import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.URISyntaxException; @@ -359,6 +361,53 @@ public int pumpkins( return affected; } + @Command( + name = "/feature", + //FAWE start + aliases = {"/placefeature"}, + //FAWE end + desc = "Generate Minecraft features" + ) + @Logging(PLACEMENT) + @CommandPermissions("worldedit.generation.feature") + public int feature( + Actor actor, LocalSession session, EditSession editSession, + @Arg(desc = "Type of feature to place", def = "forest_rock") + ConfiguredFeatureType feature + ) throws WorldEditException { + //FAWE start + int affected = editSession.generateFeature(feature, session.getPlacementPosition(actor)); + + if (affected == 0) { + actor.print(Caption.of("worldedit.generate.feature.failed")); + } else { + actor.print(Caption.of("worldedit.feature.created", TextComponent.of(affected))); + } + return affected; + //FAWE end + } + + @Command( + name = "/structure", + desc = "Generate Minecraft structures" + ) + @CommandPermissions("worldedit.generation.structure") + @Logging(POSITION) + public int structure(Actor actor, LocalSession session, EditSession editSession, + @Arg(desc = "The structure") + StructureType feature) throws WorldEditException { + //FAWE start + int affected = editSession.generateStructure(feature, session.getPlacementPosition(actor)); + + if (affected > 0) { + actor.printInfo(Caption.of("worldedit.structure.created", TextComponent.of(affected))); + } else { + actor.printError(Caption.of("worldedit.structure.failed")); + } + return affected; + //FAWE end + } + @Command( name = "/hpyramid", desc = "Generate a hollow pyramid" @@ -767,7 +816,7 @@ public int blob( if (actor instanceof Player && Settings.settings().GENERAL.UNSTUCK_ON_GENERATE) { ((Player) actor).findFreePosition(); } - actor.print(Caption.of("worldedit.sphere.created", TextComponent.of(affected))); + actor.print(Caption.of("worldedit.blob.created", TextComponent.of(affected))); return affected; } //FAWE end diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java index f3be414693..531593b5ce 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java @@ -19,6 +19,8 @@ package com.sk89q.worldedit.command; +import com.fastasyncworldedit.core.command.tool.FeaturePlacer; +import com.fastasyncworldedit.core.command.tool.StructurePlacer; import com.fastasyncworldedit.core.command.tool.brush.InspectBrush; import com.fastasyncworldedit.core.configuration.Caption; import com.google.common.collect.Collections2; @@ -56,6 +58,8 @@ import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; import com.sk89q.worldedit.util.formatting.text.format.TextColor; import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import org.enginehub.piston.CommandManager; import org.enginehub.piston.CommandManagerService; import org.enginehub.piston.CommandMetadata; @@ -244,6 +248,36 @@ public void tree( setTool(player, session, new TreePlanter(type), "worldedit.tool.tree.equip"); } + //FAWE start + @Command( + name = "featureplacer", + aliases = {"/featureplacer", "featuretool", "/featuretool"}, + desc = "Feature placer tool" + ) + @CommandPermissions("worldedit.tool.feature") + public void feature( + Player player, LocalSession session, + @Arg(desc = "Type of feature to place", def = "forest_rock") + ConfiguredFeatureType feature + ) throws WorldEditException { + setTool(player, session, new FeaturePlacer(feature), "worldedit.tool.feature.equip"); + } + + @Command( + name = "structureplacer", + aliases = {"/structureplacer", "structuretool", "/structuretool"}, + desc = "Structure placer tool" + ) + @CommandPermissions("worldedit.tool.structure") + public void structure( + Player player, LocalSession session, + @Arg(desc = "Type of structure to place", def = "") + StructureType feature + ) throws WorldEditException { + setTool(player, session, new StructurePlacer(feature), "worldedit.tool.structure.equip"); + } + //FAWE end + @Command( name = "stacker", desc = "Block stacker tool" diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java index 271c0aa072..c9b69cc30c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java @@ -32,6 +32,8 @@ import com.sk89q.worldedit.world.fluid.FluidCategory; import com.sk89q.worldedit.world.fluid.FluidType; import com.sk89q.worldedit.world.gamemode.GameMode; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.item.ItemCategory; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.weather.WeatherType; @@ -62,7 +64,9 @@ public static void register(CommandManager commandManager) { FluidType.class, FluidCategory.class, GameMode.class, - WeatherType.class + WeatherType.class, + ConfiguredFeatureType.class, + StructureType.class ) .stream() .map(c -> (Class) c) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/FeatureGeneratorFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/FeatureGeneratorFactory.java new file mode 100644 index 0000000000..6434f016bb --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/FeatureGeneratorFactory.java @@ -0,0 +1,44 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.command.factory; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.function.Contextual; +import com.sk89q.worldedit.function.EditContext; +import com.sk89q.worldedit.function.generator.FeatureGenerator; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; + +public final class FeatureGeneratorFactory implements Contextual { + private final ConfiguredFeatureType type; + + public FeatureGeneratorFactory(ConfiguredFeatureType type) { + this.type = type; + } + + @Override + public FeatureGenerator createFromContext(EditContext input) { + return new FeatureGenerator((EditSession) input.getDestination(), type); + } + + @Override + public String toString() { + return "feature of type " + type; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/Pre13HangingCompatibilityHandler.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/Pre13HangingCompatibilityHandler.java index f0bea016f2..8a72489f1c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/Pre13HangingCompatibilityHandler.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/legacycompat/Pre13HangingCompatibilityHandler.java @@ -29,7 +29,7 @@ public class Pre13HangingCompatibilityHandler implements EntityNBTCompatibilityH @Override public LinCompoundTag updateNbt(EntityType type, LinCompoundTag tag) { - if (!type.getId().startsWith("minecraft:")) { + if (!type.id().startsWith("minecraft:")) { return tag; } Direction newDirection; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/FeatureGenerator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/FeatureGenerator.java new file mode 100644 index 0000000000..f01a588db9 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/FeatureGenerator.java @@ -0,0 +1,55 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.generator; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.function.RegionFunction; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; + +/** + * Generate a feature at the given location + * + * @since TODO + */ +public class FeatureGenerator implements RegionFunction { + + private final ConfiguredFeatureType featureType; + private final EditSession editSession; + + /** + * Create a new instance. + * + * @param editSession the edit session + * @param featureType the feature type + * + * @since TODO + */ + public FeatureGenerator(EditSession editSession, ConfiguredFeatureType featureType) { + this.editSession = editSession; + this.featureType = featureType; + } + + @Override + public boolean apply(BlockVector3 position) throws WorldEditException { + return editSession.getWorld().generateFeature(featureType, editSession, position); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java index 553792599a..7c4f9c12e3 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java @@ -46,6 +46,8 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.weather.WeatherType; import javax.annotation.Nullable; @@ -311,6 +313,34 @@ default boolean regenerate(Region region, Extent extent, RegenOptions options) { boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException; + /** + * Generate a structure at the given position + * + * @param type The structure type + * @param editSession The {@link EditSession} + * @param position The position + * @return True if the generation was successful + * + * @since TODO + */ + default boolean generateStructure(StructureType type, EditSession editSession, BlockVector3 position) { + return false; + } + + /** + * Generate a feature at the given position. + * + * @param type The feature type + * @param editSession The {@link EditSession} + * @param position The position + * @return True if the generation was successful + * + * @since TODO + */ + default boolean generateFeature(ConfiguredFeatureType type, EditSession editSession, BlockVector3 position) { + return false; + } + /** * Load the chunk at the given position if it isn't loaded. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk13.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk13.java index 9b3d54027b..34e843f8fc 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk13.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk13.java @@ -131,7 +131,9 @@ public AnvilChunk13(LinCompoundTag tag) throws DataException { try { blockState = getBlockStateWith(blockState, property, value); } catch (IllegalArgumentException e) { - throw new InvalidFormatException("Invalid block state for " + blockState.getBlockType().id() + ", " + property.getName() + ": " + value); + throw new InvalidFormatException("Invalid block state for " + blockState + .getBlockType() + .id() + ", " + property.getName() + ": " + value); } } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk18.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk18.java index c5736bfd5f..79ffa8b7a8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk18.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/chunk/AnvilChunk18.java @@ -105,7 +105,9 @@ public AnvilChunk18(CompoundTag tag) throws DataException { try { blockState = getBlockStateWith(blockState, property, value); } catch (IllegalArgumentException e) { - throw new InvalidFormatException("Invalid block state for " + blockState.getBlockType().getId() + ", " + property.getName() + ": " + value); + throw new InvalidFormatException("Invalid block state for " + blockState + .getBlockType() + .id() + ", " + property.getName() + ": " + value); } } } @@ -197,9 +199,9 @@ private CompoundTag getBlockTileEntity(BlockVector3 position) throws DataExcepti @Override public BaseBlock getBlock(BlockVector3 position) throws DataException { - int x = position.getX() - rootX * 16; - int y = position.getY(); - int z = position.getZ() - rootZ * 16; + int x = position.x() - rootX * 16; + int y = position.y(); + int z = position.z() - rootZ * 16; int section = y >> 4; int yIndex = y & 0x0F; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidType.java index 1b191f07f3..1aca4c25f7 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidType.java @@ -40,17 +40,18 @@ public FluidType(String id) { } /** - * Gets the ID of this block. + * Gets the ID of this fluid. * * @return The id * @since 2.11.0 */ + @Override public String id() { return this.id; } /** - * Gets the ID of this block. + * Gets the ID of this fluid. * * @return The id * @deprecated use {@link #id()} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/ConfiguredFeatureType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/ConfiguredFeatureType.java new file mode 100644 index 0000000000..48c61a01cc --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/ConfiguredFeatureType.java @@ -0,0 +1,49 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.world.generation; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.registry.Keyed; +import com.sk89q.worldedit.registry.NamespacedRegistry; + +public record ConfiguredFeatureType(String id) implements Keyed { + + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("configured feature type"); + + @Override + public String toString() { + return this.id; + } + + //FAWE start + + /** + * Place this feature into an {@link EditSession} + * + * @param extent EditSession to place into + * @param position position to use for placement + * @return true if successful + */ + public boolean place(EditSession extent, BlockVector3 position) { + return extent.getWorld().generateFeature(this, extent, position); + } + //FAWE end +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/StructureType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/StructureType.java new file mode 100644 index 0000000000..06fef05f8d --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/StructureType.java @@ -0,0 +1,49 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.world.generation; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.registry.Keyed; +import com.sk89q.worldedit.registry.NamespacedRegistry; + +public record StructureType(String id) implements Keyed { + + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("structure type"); + + @Override + public String toString() { + return this.id; + } + + //FAWE start + + /** + * Place this structure into an {@link EditSession} + * + * @param extent EditSession to place into + * @param position position to use for placement + * @return true if successful + */ + public boolean place(EditSession extent, BlockVector3 position) { + return extent.getWorld().generateStructure(this, extent, position); + } + //FAWE end +} diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index 6b7ab19909..d8908f88fe 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -513,11 +513,17 @@ "worldedit.hcyl.thickness-too-large": "Thickness cannot be larger than x or z radii.", "worldedit.sphere.invalid-radius": "You must either specify 1 or 3 radius values.", "worldedit.sphere.created": "{0} blocks have been created.", + "worldedit.blob.created": "{0} blocks have been created.", + "worldedit.feature.created": "Feature created, {0} blocks placed.", + "worldedit.generate.feature.failed": "This feature cannot go here. Ensure the area meets the requirements.", "worldedit.forestgen.created": "{0} trees created.", "worldedit.pumpkins.created": "{0} pumpkin patches created.", + "worldedit.feature.failed": "Failed to generate feature. Is it a valid spot for it?", "worldedit.pyramid.created": "{0} blocks have been created.", "worldedit.generate.created": "{0} blocks have been created.", "worldedit.generatebiome.changed": "{0} biomes affected.", + "worldedit.structure.created": "Structure created, {0} blocks placed.", + "worldedit.structure.failed": "Failed to generate structure. Is it a valid spot for it?", "worldedit.reload.config": "Configuration reloaded!", "worldedit.report.written": "FAWE report written to {0}", "worldedit.report.error": "Failed to write report: {0}", @@ -555,6 +561,8 @@ "worldedit.tool.deltree.not-floating": "That's not a floating tree.", "worldedit.tool.tree.equip": "Tree tool bound to {0}.", "worldedit.tool.tree.obstructed": "A tree can't go there.", + "worldedit.tool.feature.equip": "Feature placer tool bound to {0}.", + "worldedit.tool.feature.obstructed": "This feature cannot go there. Ensure the area meets the requirements.", "worldedit.tool.info.equip": "Info tool bound to {0}.", "worldedit.tool.inspect.equip": "Inspect tool bound to {0}.", "worldedit.tool.info.blockstate.hover": "Block state",