diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/FaweBlockStateListPopulator.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/FaweBlockStateListPopulator.java new file mode 100644 index 000000000..acd4823c5 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/FaweBlockStateListPopulator.java @@ -0,0 +1,93 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_17_R1_2; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeManager; +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_17_R1.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 Biome 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 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 FluidState getFluidIfLoaded(final BlockPos blockposition) { + return world.getFluidIfLoaded(blockposition); + } + + @Override + public WorldBorder getWorldBorder() { + return world.getWorldBorder(); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightFaweAdapter.java index 2029be6f9..273d43590 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightFaweAdapter.java @@ -14,6 +14,7 @@ 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.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -53,35 +54,48 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; 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 net.minecraft.core.BlockPos; import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; import net.minecraft.core.WritableRegistry; import net.minecraft.nbt.IntTag; import net.minecraft.network.protocol.game.ClientboundLevelChunkPacket; 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.Level; 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.chunk.LevelChunkSection; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature; +import net.minecraft.world.level.levelgen.feature.configurations.StructureFeatureConfiguration; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.StructureStart; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.TreeType; +import org.bukkit.World; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_17_R1.CraftChunk; import org.bukkit.craftbukkit.v1_17_R1.CraftServer; @@ -105,6 +119,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.OptionalInt; +import java.util.Random; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -589,6 +604,164 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements return true; } + @Override + public boolean generateFeature(ConfiguredFeatureType feature, World world, EditSession editSession, BlockVector3 pt) { + ServerLevel serverLevel = ((CraftWorld) world).getHandle(); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + ConfiguredFeature configuredFeature = serverLevel + .registryAccess() + .registryOrThrow(Registry.CONFIGURED_FEATURE_REGISTRY) + .get(ResourceLocation.tryParse(feature.getId())); + + Map placed = TaskManager.taskManager().sync(() -> { + serverLevel.captureTreeGeneration = true; + serverLevel.captureBlockStates = true; + try { + if (!configuredFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.getX(), pt.getY(), pt.getZ()) + )) { + return null; + } + return populator.getList().stream().collect(Collectors.toMap(CraftBlockState::getPosition, + craftBlockState -> craftBlockState + )); + } 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(); + ConfiguredStructureFeature k = serverLevel + .registryAccess() + .registryOrThrow(Registry.CONFIGURED_STRUCTURE_FEATURE_REGISTRY) + .get(ResourceLocation.tryParse(type.getId())); + if (k == null) { + return false; + } + + ServerChunkCache chunkManager = serverLevel.getChunkSource(); + + BlockPos pos = new BlockPos(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ()); + ChunkPos chunkPos = new ChunkPos(pos); + + //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(), + serverLevel.getStructureManager(), + serverLevel.getSeed(), + chunkPos, + serverLevel.getBiome(pos), + 0, + (StructureFeatureConfiguration) k.config, + serverLevel + ); + 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( + serverLevel, + serverLevel.structureFeatureManager(), + chunkManager.getGenerator(), + serverLevel.getRandom(), + new BoundingBox( + chunkPosx.getMinBlockX(), + serverLevel.getMinBuildHeight(), + chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), + serverLevel.getMaxBuildHeight(), + chunkPosx.getMaxBlockZ() + ), + chunkPosx + )); + return populator.getList().stream().collect(Collectors.toMap( + CraftBlockState::getPosition, + craftBlockState -> craftBlockState + )); + } + } 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 = new net.minecraft.nbt.CompoundTag(); + blockEntity.save(tag); + 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(Registry.CONFIGURED_FEATURE_REGISTRY).keySet()) { + if (ConfiguredFeatureType.REGISTRY.get(name.toString()) == null) { + ConfiguredFeatureType.REGISTRY.register(name.toString(), new ConfiguredFeatureType(name.toString())); + } + } + + // Structures + for (ResourceLocation name : server.registryAccess().registryOrThrow(Registry.STRUCTURE_FEATURE_REGISTRY).keySet()) { + if (StructureType.REGISTRY.get(name.toString()) == null) { + StructureType.REGISTRY.register(name.toString(), new StructureType(name.toString())); + } + } + } + @Override public List getEntities(org.bukkit.World world) { // Quickly add each entity to a list copy. diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/FaweBlockStateListPopulator.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/FaweBlockStateListPopulator.java new file mode 100644 index 000000000..43e99c245 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/FaweBlockStateListPopulator.java @@ -0,0 +1,100 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_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.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_18_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 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(); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java index 49125529d..adfc5537e 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java @@ -14,6 +14,7 @@ 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.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -53,36 +54,48 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; 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.nbt.IntTag; 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.Level; 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.chunk.LevelChunkSection; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.StructureStart; import org.apache.logging.log4j.Logger; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.TreeType; +import org.bukkit.World; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_18_R2.CraftChunk; import org.bukkit.craftbukkit.v1_18_R2.CraftServer; @@ -581,6 +594,163 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements return true; } + @Override + public boolean generateFeature(ConfiguredFeatureType feature, World world, EditSession editSession, BlockVector3 pt) { + ServerLevel serverLevel = ((CraftWorld) world).getHandle(); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + Registry> registry = serverLevel + .registryAccess() + .registryOrThrow(Registry.CONFIGURED_FEATURE_REGISTRY); + ConfiguredFeature configuredFeature = registry.get(ResourceLocation.tryParse(feature.getId())); + 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.getX(), pt.getY(), pt.getZ()) + )) { + return null; + } + return populator.getList().stream().collect(Collectors.toMap( + CraftBlockState::getPosition, + craftBlockState -> craftBlockState + )); + } 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(); + ConfiguredStructureFeature k = serverLevel + .registryAccess() + .registryOrThrow(Registry.CONFIGURED_STRUCTURE_FEATURE_REGISTRY) + .get(ResourceLocation.tryParse(type.getId())); + if (k == null) { + return false; + } + + ServerChunkCache chunkManager = serverLevel.getChunkSource(); + + ChunkPos chunkPos = new ChunkPos(new BlockPos(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ())); + + //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(), + serverLevel.getStructureManager(), + serverLevel.getSeed(), + chunkPos, + 0, + serverLevel, + 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( + serverLevel, + serverLevel.structureFeatureManager(), + chunkManager.getGenerator(), + serverLevel.getRandom(), + new BoundingBox( + chunkPosx.getMinBlockX(), + serverLevel.getMinBuildHeight(), + chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), + serverLevel.getMaxBuildHeight(), + chunkPosx.getMaxBlockZ() + ), + chunkPosx + )); + return populator.getList().stream().collect(Collectors.toMap( + CraftBlockState::getPosition, + craftBlockState -> craftBlockState + )); + } + } 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(Registry.CONFIGURED_FEATURE_REGISTRY).keySet()) { + if (ConfiguredFeatureType.REGISTRY.get(name.toString()) == null) { + ConfiguredFeatureType.REGISTRY.register(name.toString(), new ConfiguredFeatureType(name.toString())); + } + } + + // Structures + for (ResourceLocation name : server.registryAccess().registryOrThrow(Registry.STRUCTURE_FEATURE_REGISTRY).keySet()) { + if (StructureType.REGISTRY.get(name.toString()) == null) { + StructureType.REGISTRY.register(name.toString(), new StructureType(name.toString())); + } + } + } + @Override public List getEntities(org.bukkit.World world) { // Quickly add each entity to a list copy. diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/FaweBlockStateListPopulator.java b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/FaweBlockStateListPopulator.java new file mode 100644 index 000000000..1e9eef179 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/FaweBlockStateListPopulator.java @@ -0,0 +1,106 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_19_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.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_19_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(); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightFaweAdapter.java index 82aba5fb6..488913a0a 100644 --- a/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_19_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_19_R3/PaperweightFaweAdapter.java @@ -14,6 +14,7 @@ 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.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -51,11 +52,14 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; 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; @@ -63,23 +67,31 @@ 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; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.TreeType; +import org.bukkit.World; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_19_R3.CraftServer; import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; @@ -544,6 +556,164 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements return true; } + @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.getId())); + 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.getX(), pt.getY(), pt.getZ()) + )) { + return null; + } + return populator.getList().stream().collect(Collectors.toMap( + CraftBlockState::getPosition, + craftBlockState -> craftBlockState + )); + } 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.getId())); + if (k == null) { + return false; + } + + ServerChunkCache chunkManager = serverLevel.getChunkSource(); + + ChunkPos chunkPos = new ChunkPos(new BlockPos(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ())); + + //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, + serverLevel, + 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( + serverLevel, + serverLevel.structureManager(), + chunkManager.getGenerator(), + serverLevel.getRandom(), + new BoundingBox( + chunkPosx.getMinBlockX(), + serverLevel.getMinBuildHeight(), + chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), + serverLevel.getMaxBuildHeight(), + chunkPosx.getMaxBlockZ() + ), + chunkPosx + )); + return populator.getList().stream().collect(Collectors.toMap( + CraftBlockState::getPosition, + craftBlockState -> craftBlockState + )); + } + } 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); diff --git a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightServerLevelDelegateProxy.java index 23d4aa821..17b10a2d4 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightServerLevelDelegateProxy.java +++ b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R1/PaperweightServerLevelDelegateProxy.java @@ -35,7 +35,7 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -public class PaperweightServerLevelDelegateProxy implements InvocationHandler { +public class PaperweightServerLevelDelegateProxy implements InvocationHandler { private final EditSession editSession; private final ServerLevel serverLevel; diff --git a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/FaweBlockStateListPopulator.java b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/FaweBlockStateListPopulator.java new file mode 100644 index 000000000..9989ed781 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/FaweBlockStateListPopulator.java @@ -0,0 +1,106 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_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.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_R1.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(); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightFaweAdapter.java index aad40c611..5310e51d1 100644 --- a/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R1/PaperweightFaweAdapter.java @@ -14,6 +14,7 @@ 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.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -51,11 +52,14 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; 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; @@ -63,23 +67,31 @@ 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; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.TreeType; +import org.bukkit.World; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_20_R1.CraftServer; import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; @@ -544,6 +556,165 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements return true; } + @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.getId())); + 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.getX(), pt.getY(), pt.getZ()) + )) { + return null; + } + return populator.getList().stream().collect(Collectors.toMap( + CraftBlockState::getPosition, + craftBlockState -> craftBlockState + )); + } 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.getId())); + if (k == null) { + return false; + } + + ServerChunkCache chunkManager = serverLevel.getChunkSource(); + + ChunkPos chunkPos = new ChunkPos(new BlockPos(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ())); + + //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, + serverLevel, + 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( + serverLevel, + serverLevel.structureManager(), + chunkManager.getGenerator(), + serverLevel.getRandom(), + new BoundingBox( + chunkPosx.getMinBlockX(), + serverLevel.getMinBuildHeight(), + chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), + serverLevel.getMaxBuildHeight(), + chunkPosx.getMaxBlockZ() + ), + chunkPosx + )); + return populator.getList().stream().collect(Collectors.toMap( + CraftBlockState::getPosition, + craftBlockState -> craftBlockState + )); + } + } 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); 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 b73e6d06e..31f4736e0 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 @@ -305,6 +305,21 @@ public final class PaperweightAdapter implements BukkitImplAdapter + * 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.bukkit.adapter.ext.fawe.v1_20_R2; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +public class PaperweightServerLevelDelegateProxy implements InvocationHandler { + + private final EditSession editSession; + private final ServerLevel serverLevel; + private final PaperweightAdapter adapter; + + private PaperweightServerLevelDelegateProxy(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + this.editSession = editSession; + this.serverLevel = serverLevel; + this.adapter = adapter; + } + + public static WorldGenLevel newInstance(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + return (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter) + ); + } + + @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.load((CompoundTag) adapter.fromNativeBinary(this.editSession.getFullBlock(BlockVector3.at(blockPos.getX(), + blockPos.getY(), blockPos.getZ())).getNbtReference().getValue())); + + return newEntity; + } + + private BlockState getBlockState(BlockPos blockPos) { + 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(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), adapter.adapt(blockState)); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + private boolean removeBlock(BlockPos blockPos, boolean bl) { + try { + return editSession.setBlock(BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()), BlockTypes.AIR.getDefaultState()); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch (method.getName()) { + case "a_", "getBlockState" -> { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockState + return getBlockState(blockPos); + } + } + case "c_", "getBlockEntity" -> { + if (args.length == 1 && args[0] instanceof BlockPos blockPos) { + // getBlockEntity + return getBlockEntity(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 -> { } + } + + return method.invoke(this.serverLevel, args); + } + +} 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 000000000..a802a2e7a --- /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 48bc935cb..d1bc6b0cf 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 @@ -14,12 +14,14 @@ 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.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.BukkitWorld; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2.PaperweightServerLevelDelegateProxy; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen.PaperweightRegen; import com.sk89q.worldedit.entity.BaseEntity; @@ -50,11 +52,14 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; 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; @@ -62,23 +67,32 @@ 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.WorldGenLevel; 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; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.TreeType; +import org.bukkit.World; import org.bukkit.block.data.BlockData; import org.bukkit.craftbukkit.v1_20_R2.CraftServer; import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; @@ -89,6 +103,7 @@ import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; import org.bukkit.craftbukkit.v1_20_R2.util.CraftNamespacedKey; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; import java.lang.ref.WeakReference; @@ -96,6 +111,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -104,6 +120,7 @@ import java.util.Map; import java.util.Objects; import java.util.OptionalInt; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -547,6 +564,168 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements return true; } + @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.getId())); + 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.getX(), pt.getY(), pt.getZ()) + )) { + return null; + } + return populator.getList().stream().collect(Collectors.toMap( + CraftBlockState::getPosition, + craftBlockState -> craftBlockState + )); + } 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.getId())); + if (k == null) { + return false; + } + + ServerChunkCache chunkManager = serverLevel.getChunkSource(); + + ChunkPos chunkPos = new ChunkPos(new BlockPos(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ())); + + //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); 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 29162b607..4e2e6e8d5 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 @@ -392,4 +392,12 @@ public interface IBukkitAdapter { 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/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index e881416c5..84f769edb 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 @@ package com.sk89q.worldedit.bukkit; 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; @@ -247,6 +247,7 @@ public class WorldEditPlugin extends JavaPlugin { 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-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 000000000..3a74f14a0 --- /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 000000000..ae47acada --- /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 000000000..99720f65d --- /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 feature + * + * @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 000000000..537a6c83b --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/function/generator/StructureGenerator.java @@ -0,0 +1,30 @@ +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; + +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 + */ + 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 05616a414..3189d7a31 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 @@ -36,6 +36,8 @@ import com.sk89q.worldedit.world.block.BaseBlock; 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; @@ -132,12 +134,10 @@ public class WorldWrapper extends AbstractWorld { 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) { @@ -206,12 +206,10 @@ public class WorldWrapper extends AbstractWorld { 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 @@ -261,12 +259,15 @@ public class WorldWrapper extends AbstractWorld { }); } - //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) { @@ -288,6 +289,16 @@ public class WorldWrapper extends AbstractWorld { } } + @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 ecf859896..f9b71dc6d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -147,6 +147,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.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; @@ -4017,5 +4019,29 @@ public class EditSession extends PassthroughExtent implements AutoCloseable { } return changes; } + + /** + * Generate a feature into this EditSession + * + * @param feature feature to generate + * @param position position to generate at + * @return blocks affected + */ + 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 + */ + 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 c0c1f77a0..cd564d0ff 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; @@ -116,8 +117,9 @@ import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; -import org.anarres.parallelgzip.ParallelGZIPOutputStream; 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; import org.enginehub.piston.annotation.param.Arg; @@ -1479,6 +1481,24 @@ public class BrushCommands { } //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 1ae9f10ce..e311250ad 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 @@ -64,7 +64,7 @@ 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; @@ -349,19 +349,28 @@ public class GenerationCommands { @Command( name = "/feature", + //FAWE start + aliases = {"/placefeature"}, + //FAWE end desc = "Generate Minecraft features" ) + @Logging(PLACEMENT) @CommandPermissions("worldedit.generation.feature") - @Logging(POSITION) - public int feature(Actor actor, LocalSession session, EditSession editSession, - @Arg(desc = "The feature") - ConfiguredFeatureType feature) throws WorldEditException { - if (editSession.getWorld().generateFeature(feature, editSession, session.getPlacementPosition(actor))) { - actor.printInfo(Caption.of("worldedit.feature.created")); + 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.printError(Caption.of("worldedit.feature.failed")); + actor.print(Caption.of("worldedit.feature.created", TextComponent.of(affected))); } - return 0; + return affected; + //FAWE end } @Command( @@ -373,12 +382,16 @@ public class GenerationCommands { public int structure(Actor actor, LocalSession session, EditSession editSession, @Arg(desc = "The structure") StructureType feature) throws WorldEditException { - if (editSession.getWorld().generateStructure(feature, editSession, session.getPlacementPosition(actor))) { - actor.printInfo(Caption.of("worldedit.structure.created")); + //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 0; + return affected; + //FAWE end } @Command( @@ -750,7 +763,7 @@ public class GenerationCommands { ) @Logging(PLACEMENT) @CommandPermissions("worldedit.generation.blob") - public int blobBrush( + public int blob( Actor actor, LocalSession session, EditSession editSession, @Arg(desc = "Pattern") Pattern pattern, @@ -780,7 +793,7 @@ public class GenerationCommands { if (actor instanceof Player) { ((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 8395b2e2d..85ea94b65 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.TextComponent; 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 class ToolCommands { 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/world/World.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java index ebcaf6008..925788b57 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 @@ -313,6 +313,14 @@ public interface World extends Extent, Keyed, IChunkCache { 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 + */ default boolean generateStructure(StructureType type, EditSession editSession, BlockVector3 position) { return false; } @@ -320,9 +328,9 @@ public interface World extends Extent, Keyed, IChunkCache { /** * Generate a feature at the given position. * - * @param type The feature type + * @param type The feature type * @param editSession The {@link EditSession} - * @param position The position + * @param position The position * @return True if the generation was successful */ default boolean generateFeature(ConfiguredFeatureType type, EditSession editSession, BlockVector3 position) { 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 index 56888acd9..bc5126d31 100644 --- 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 @@ -19,6 +19,8 @@ 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; @@ -40,4 +42,17 @@ public class ConfiguredFeatureType implements Keyed { 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 index e4e8aaebb..0713735d7 100644 --- 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 @@ -19,6 +19,8 @@ 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; @@ -40,4 +42,17 @@ public class StructureType implements Keyed { 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 1e34364e2..4faa8b1b5 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -496,14 +496,16 @@ "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.created": "Feature 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.", + "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}", @@ -542,6 +544,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",