diff --git a/verification/src/changes/accepted-core-public-api-changes.json b/verification/src/changes/accepted-core-public-api-changes.json index 7bac704f8..8842fa552 100644 --- a/verification/src/changes/accepted-core-public-api-changes.json +++ b/verification/src/changes/accepted-core-public-api-changes.json @@ -129,6 +129,13 @@ "changes": [ "METHOD_NEW_DEFAULT" ] + }, + { + "type": "com.sk89q.worldedit.world.World", + "member": "Method com.sk89q.worldedit.world.World.generateStructure(com.sk89q.worldedit.world.generation.StructureType,com.sk89q.worldedit.EditSession,com.sk89q.worldedit.math.BlockVector3)", + "changes": [ + "METHOD_NEW_DEFAULT" + ] } ], "LazyReference was never publicly extensible": [ diff --git a/worldedit-bukkit/adapters/adapter-1.19.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_19_R3/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1.19.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_19_R3/PaperweightAdapter.java index 8cc043cd2..e45873cf1 100644 --- a/worldedit-bukkit/adapters/adapter-1.19.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_19_R3/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1.19.4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v1_19_R3/PaperweightAdapter.java @@ -65,10 +65,12 @@ import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.entity.EntityTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.SectionPos; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.ByteArrayTag; import net.minecraft.nbt.ByteTag; @@ -121,6 +123,9 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.PrimaryLevelData; import net.minecraft.world.phys.BlockHitResult; @@ -895,6 +900,13 @@ public void initializeRegistries() { 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())); + } + } } public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { @@ -905,6 +917,29 @@ public boolean generateFeature(ConfiguredFeatureType type, World world, EditSess return k != null && k.place(proxyLevel, chunkManager.getGenerator(), random, new BlockPos(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ())); } + public boolean generateStructure(StructureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + Structure k = originalWorld.registryAccess().registryOrThrow(Registries.STRUCTURE).get(ResourceLocation.tryParse(type.getId())); + if (k == null) { + return false; + } + + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + WorldGenLevel proxyLevel = PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this); + ChunkPos chunkPos = new ChunkPos(new BlockPos(pt.getBlockX(), pt.getBlockY(), pt.getBlockZ())); + StructureStart structureStart = k.generate(originalWorld.registryAccess(), chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), chunkManager.chunkMap.structureTemplateManager, originalWorld.getSeed(), chunkPos, 0, proxyLevel, biome -> true); + + if (!structureStart.isValid()) { + return false; + } else { + BoundingBox boundingBox = structureStart.getBoundingBox(); + ChunkPos min = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.minX()), SectionPos.blockToSectionCoord(boundingBox.minZ())); + ChunkPos max = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.maxX()), SectionPos.blockToSectionCoord(boundingBox.maxZ())); + ChunkPos.rangeClosed(min, max).forEach((chunkPosx) -> structureStart.placeInChunk(proxyLevel, originalWorld.structureManager(), chunkManager.getGenerator(), originalWorld.getRandom(), new BoundingBox(chunkPosx.getMinBlockX(), originalWorld.getMinBuildHeight(), chunkPosx.getMinBlockZ(), chunkPosx.getMaxBlockX(), originalWorld.getMaxBuildHeight(), chunkPosx.getMaxBlockZ()), chunkPosx)); + return true; + } + } + // ------------------------------------------------------------------------ // Code that is less likely to break // ------------------------------------------------------------------------ diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index df9db2e58..573c822f5 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -45,6 +45,7 @@ import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.weather.WeatherType; import com.sk89q.worldedit.world.weather.WeatherTypes; import io.papermc.lib.PaperLib; @@ -446,6 +447,16 @@ public boolean generateFeature(ConfiguredFeatureType type, EditSession editSessi return false; } + @Override + public boolean generateStructure(StructureType type, EditSession editSession, BlockVector3 position) { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + if (adapter != null) { + return adapter.generateStructure(type, getWorld(), editSession, position); + } + // No adapter, we can't generate this. + return false; + } + private static volatile boolean hasWarnedImplError = false; @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java index 4323fd12a..80db19186 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java @@ -38,6 +38,7 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.item.ItemType; import org.bukkit.Location; import org.bukkit.World; @@ -294,6 +295,19 @@ default boolean generateFeature(ConfiguredFeatureType feature, World world, Edit throw new UnsupportedOperationException("This adapter does not support generating features."); } + /** + * Generates a Minecraft structure at the given location. + * + * @param feature The feature + * @param world The world + * @param session The EditSession + * @param pt The location + * @return If it succeeded + */ + default boolean generateStructure(StructureType feature, World world, EditSession session, BlockVector3 pt) { + throw new UnsupportedOperationException("This adapter does not support generating features."); + } + /** * Initialize registries that require NMS access. */ 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 491942fb0..d913955a0 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 @@ -40,6 +40,7 @@ import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.param.Arg; @@ -253,6 +254,23 @@ public int feature(Actor actor, LocalSession session, EditSession editSession, return 0; } + @Command( + name = "/structure", + desc = "Generate Minecraft structures" + ) + @CommandPermissions("worldedit.generation.structure") + @Logging(POSITION) + public int structure(Actor actor, LocalSession session, EditSession editSession, + @Arg(desc = "The structure") + StructureType feature) throws WorldEditException { + if (editSession.getWorld().generateStructure(feature, editSession, session.getPlacementPosition(actor))) { + actor.printInfo(TranslatableComponent.of("worldedit.structure.created")); + } else { + actor.printError(TranslatableComponent.of("worldedit.structure.failed")); + } + return 0; + } + @Command( name = "/hpyramid", desc = "Generate a hollow pyramid" diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java index 38edcec64..ed5e2cc1a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java @@ -33,6 +33,7 @@ import com.sk89q.worldedit.world.fluid.FluidType; import com.sk89q.worldedit.world.gamemode.GameMode; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; import com.sk89q.worldedit.world.item.ItemCategory; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.weather.WeatherType; @@ -64,7 +65,8 @@ public static void register(CommandManager commandManager) { FluidCategory.class, GameMode.class, WeatherType.class, - ConfiguredFeatureType.class + ConfiguredFeatureType.class, + StructureType.class ) .stream() .map(c -> (Class) c) 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 bfbb3f7fd..a537e5bbe 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 @@ -42,6 +42,7 @@ 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 java.nio.file.Path; @@ -274,6 +275,10 @@ default boolean regenerate(Region region, Extent extent, RegenOptions options) { */ boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException; + default boolean generateStructure(StructureType type, EditSession editSession, BlockVector3 position) { + return false; + } + /** * Generate a feature at the given position. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/StructureType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/StructureType.java new file mode 100644 index 000000000..e4e8aaebb --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/StructureType.java @@ -0,0 +1,43 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.world.generation; + +import com.sk89q.worldedit.registry.Keyed; +import com.sk89q.worldedit.registry.NamespacedRegistry; + +public class StructureType implements Keyed { + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("structure type"); + + private final String id; + + public StructureType(String id) { + this.id = id; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String toString() { + return this.id; + } +}